如何使用Python实现易班自动报送

前言

emmm,我是那位上墙的“巧克力微微甜”,让大家见笑了QwQ。完整的源码在文章的最后,欢迎交流。本人蒟蒻,还望大佬轻喷···

整体思路

我们知道很多app的一些页面其实都是网页,从“分享”功能里提供了“复制链接”这个选项,可以看出来易班报送页面其实也是一个网页。而且这个网页还是唯一对应每个账户,也就是说有了这个网址连账号密码都不需要就能帮别人完成填报。
学过一点爬虫会知道python可以使用selenium+chromedriver模拟人对浏览器进行操作(其实用Request应该也是可以的,不过我不太会用)。
于是我们把这个链接捣鼓到电脑上,写个代码让浏览器自动访问填报页面并且完成填报。
如果你还不会selenium的话,可以出门左转学习一波。

以上是我代码实现的思路,除了这个思路以外还可以直接在手机上对app进行操作,这个需要用到Appium。具体实现我没尝试过。不会Appinum的可以点我(可以用来爬取你微信的朋友圈,爬啥都行)

最后一种思路是一位大佬和我交流过程中提到了adb,不过我不会adb,应该也是可以实现的。

下面开始正文

报送页面无法用网页打开?!Fiddler抓包攻破易班防线

到这一步我们手上已经有链接了(ios是无法成功拿到链接的···QAQ)。先用浏览器打开它试试。咦?一打开就跳到了易班首页。
在这里插入图片描述
这是user-agent在作祟,一个表明访问环境的字段。易班网页通过它判断环境是否为易班app,不是就拒绝访问。因此我们必须想办法获取易班使用的user-agent,然后用它伪装自己的浏览器,假装自己是易班app。

可以想到使用抓包的办法,这里我用了Fiddler对手机进行抓包。
学习Fiddler戳这
抓到包以后随意打开其中一个,在请求头里就可以看到user-agent了在这里插入图片描述
然后打开chrome浏览器,配置一个新的user-agent,命名为易班,粘贴进搞到手的那一串,再用它打开网页就可以进入了。
在这里插入图片描述
接下来用selenium在浏览器里一通操作就可以完成报送了。
然后让电脑每天定点自动执行程序就可以了
Windows用任务计划程序,Linux用crontab命令。
这里我是挂载在我的阿里云服务器上(Windows Server)

改进升级你的自动报送程序

如何实现不同的账户的自动签到

这个简单,把每个人的填报信息放进一个文件里,写个文件操作就可以了。

增强鲁棒性,应对已经报送过的用户

有时候你的用户/基友/闺蜜可能哪天突然早起或者想到了要报送,于是自己报上了,如果不考虑这个情况程序就会被卡住。在填报第一个项目的时候用上try语句就可以了。

如何提高报送效率,应对高用户量

考虑到工具用户量有可能很大(你看我做的白日梦多香),用原本的办法填报一个人经计算平均需要35s,那么一小时只能填报120人(然鹅现在只有不到10人用)
因为是网络请求操作,很多时间其实花费在等待网页的加载上,因此用上多线程。i7-7700HQ,8GB的电脑经过测试5~8线程差不多是最高效率,再多反而更慢,报送效率提升到平均一分钟15人,也就是一个人均摊4s。

如何发送提醒邮件

一开始我用selenium操作浏览器打开QQ邮箱网页版进行操作,会遇到的问题是验证码,虽然也可以解决(看这里),但是我还是改用了SMTP发送邮件的方式。

完整源码

import os
import sys
import smtplib
from email.mime.text import MIMEText
from email.utils import formataddr
from time import sleep, time
from selenium import webdriver
from threading import Thread, Lock
from random import randrange, shuffle
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.action_chains import ActionChains


class Info(object):
    def __init__(self, url, sel1, sel2, sel3, email, low, high):
        self.url = url
        self.sel1 = sel1
        self.sel2 = sel2
        self.sel3 = sel3
        self.email = email
        self.low = low
        self.high = high


def mail(my_user):
    try:
        msg = MIMEText(
            '易班报送成功。', 'plain', 'utf-8')
        msg["Accept-Language"] = "zh-CN"
        msg["Accept-Charset"] = "ISO-8859-1,utf-8"
        # 发件人邮箱暱称、发件人邮箱账号
        msg['From'] = formataddr(["Auto5", my_sender])
        # 收件人邮箱暱称、收件人邮箱账号
        msg['To'] = formataddr(["Auto5User", my_user])
        msg['Subject'] = "易班自动报送成功"                # 邮件的标题
        # print(msg.as_string())

        # 括号中对应的是发件人邮箱账号、收件人邮箱账号、发送邮件
        server.sendmail(my_sender, [my_user, ], msg.as_string())

    except Exception as e:
        print(e)


def select_element(driver, element_idx=1, selection_idx=0):
    driver.find_element_by_xpath(
        '//*[@id="WFForm"]/div[2]/div/div[' +
        str(element_idx)+']/div/div[1]').click()
    sleep(1)
    dragger = driver.find_element_by_xpath(
        '/html/body/div[2]/div/div[2]/div/div/div/div/div[2]/div/div[2]')
    ActionChains(driver).drag_and_drop_by_offset(
        dragger, 0, -50*selection_idx).perform()
    sleep(1)
    driver.find_element_by_xpath(
        '/html/body/div[2]/div/div[2]/div/div/div/div/div[1]/div[3]').click()
    sleep(1)


def random_temperature(low=3, high=8):
    return "36." + str(randrange(low, high, 1))


def operate(infos=[]):
    user_count = len(infos)
    if user_count == 0:
        return
    # 随机顺序
    shuffle(infos)
    # 随机延时上下限计算
    # 计算公式 最长时间=(3600-100*用户数)/(用户数)
    # 计算公式 最短时间=最长时间/2
    most = (3600-100*user_count)/user_count
    least = most/2
    for idx in range(len(infos)):
        # mail(infos[idx].email)
        # 随机延时
        sleep_time = randrange(least, most)
        print(sleep_time)
        sleep(sleep_time)
        # 进行操作
        options = webdriver.ChromeOptions()
        options.add_argument(
            'user-agent="Mozilla/5.0 (Linux Android 8.0.0 MIX 2 Build/OPR1.170623.027 wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/71.0.3578.99 Mobile Safari/537.36 yiban_android"')
        driver = webdriver.Chrome(chrome_options=options)
        driver.maximize_window()
        driver.get(infos[idx].url)
        sleep(1)
        # 点击任务
        driver.find_element_by_xpath(
            '//*[@id="root"]/div[3]/div[1]/div[3]').click()
        sleep(1)
        # 点击第一个信息采集栏目
        driver.find_element_by_xpath(
            '//*[@id = "root"]/div/div[1]/div[1]/div/div/div[1]/div').click()
        sleep(1)
        # 点击反馈
        driver.find_element_by_xpath(
            '//*[@id = "root"]/div/footer/a').click()
        sleep(1)
        # 以下填报各项信息
        # 打开第一个信息栏目
        try:
            select_element(driver, 1, infos[idx].sel1)
        except Exception as e:
            print("Fail")
            print(e)
        else:
            select_element(driver, 2, infos[idx].sel2)
            select_element(driver, 3, infos[idx].sel3)
            driver.find_element_by_xpath(
                '//*[@id="WFForm"]/div[2]/div/div[4]/div/div[2]/input').send_keys(random_temperature(infos[idx].low, infos[idx].high))
            sleep(1)
            # 提交
            driver.find_element_by_xpath(
                '//*[@id = "root"]/div/a').click()
            # 发送提示邮件
            mail(infos[idx].email)
        finally:
            # print(infos[idx].email)
            # mail(infos[idx].email)
            # 关闭浏览器
            sleep(1)
            driver.close()
    driver.quit()


my_sender = ''    # 发件人邮箱账号
my_pass = ''       # 发件人邮箱密码
# 登录邮箱
server = smtplib.SMTP_SSL("smtp.qq.com", 465)  # 用发件人邮箱的SMTP服务器地址和端口创建一个新的连接实例
server.login(my_sender, my_pass)  # 登录
# 获取绝对路径并打开数据文件
path = os.path.abspath(os.path.dirname(sys.argv[0]))
fp = open(path + "/test.dat", "r")
# 线程数设定
# 当前用户数较少设定为1
# TODO 自适应线程数功能
thread_num = 1
# thread_num = int(input("thread_num = "))
# 根据线程数设定建立对应数量的list存放task
thread_tasks = []
for _ in range(thread_num):
    thread_tasks.append([])
# 从文件读取数据并加入list
now_thread = -1
while True:
    now_thread += 1
    # 读取用户易班链接
    url = fp.readline()
    # 检测文件是否已经结束
    if url == "":
        break
    # 读取用户三个选项的选择
    sel1 = int(fp.readline())
    sel2 = int(fp.readline())
    sel3 = int(fp.readline())
    # 读取用户邮箱地址并去掉末尾的\n\r
    # 如果不去掉换行符会导致邮件内容乱码
    email = fp.readline()
    email = email.replace('\n', '').replace('\r', '')
    # 读入用户设定的最低和最高体温
    low = int(fp.readline())
    high = int(fp.readline())
    # 将当前用户分配给线程
    thread_tasks[now_thread % thread_num].append(
        Info(url, sel1, sel2, sel3, email, low, high))
# 读取完毕,关闭文件
fp.close()
# 开始多线程操作
threads = []
# start = time()
for now_tasks in thread_tasks:
    t = Thread(target=operate, args=(now_tasks,))
    threads.append(t)
    t.start()
    sleep(0.5)
# 等待所有线程执行完毕
for t in threads:
    t.join()
# end = time()
# print('总共耗费了%.2f秒.' % (end - start))

# 向自己发送提示邮件,表明程序正常运行结束,已经完成所有用户的签到。
mail('')
# 关闭邮箱连接
server.quit()

缺陷

对网页的依赖性

网页源代码一旦改变工具就会失效,必须马上更新,没得办法的硬伤。

ios无法获取填报页面链接

我也好无奈

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章