接口测试 来点高级货:手搓自动化验证码登录注册自动化测试

NiMail · 2025年10月27日 · 最后由 NiMail 回复于 2025年10月29日 · 4409 次阅读

以前提到验证码注册登录自动化问题就堵死,也确实是,问题不是在于技术难度,代码难写,而是在于没有验证码接口呀,巧妇难为无米之炊!

现在我自己手搓了一个邮件服务接口,直接提供验证码查询 API 出来。

当前的思路是这样,我下面直接输出接口代码:

第一步:随机一个 https://www.nimai.cn 的临时邮箱(如:x2sd332@nimail.cn),调用业务接口,发送验证码:

第二步 1:查询邮件服务接口,用上面随机生成的邮箱(x2sd332@nimail.cn)去循环查询邮件。
关键在这里,需要用邮箱去查邮件,这里可能返回多个邮件,拿最新的一个就行:
curl 'https://www.nimail.cn/api/getmails' \
-H 'accept: application/json, text/javascript, /; q=0.01' \
-H 'accept-language: zh-CN,zh;q=0.9' \
-H 'origin: https://www.nimail.cn' \
--data-raw 'mail=ohgim96m%40nimail.cn'

第二步 2:上面查询到数据后,使用字段 id 的值,请求这个接口获取邮件内容。
curl 'https://www.nimail.cn/api/raw-html/ohgim96m@nimail.cn/1761577116655'

第三步:查询到的邮件后,对邮件进行正则匹配,匹配到你的验证码,提取出来。

第四步:用验证码和第一步调用业务接口获取到的随机字符串一起,请求验证接口或者注册接口

第五步:验证完成。

代码示例如下:

import requests
import random
import string
import time
import re
import json
import urllib.parse

class EmailVerification:
    def __init__(self):
        self.base_url = "http://localhost:8888/v1/user"
        self.nimail_api = "https://www.nimail.cn/api"

    def generate_random_email(self):
        """生成随机nimail邮箱"""
        # 生成随机用户名(6-10位字母数字)
        username_length = random.randint(6, 10)
        username = ''.join(random.choices(string.ascii_lowercase + string.digits, k=username_length))

        return f"{username}@nimail.cn"

    def send_verification_code(self, email):
        """第一步:发送验证码"""
        url = f"{self.base_url}/sendEmailCode"

        headers = {
            'Accept-Language': 'en-US',
            'Content-Type': 'text/plain'
        }

        data = {
            "email": email
        }

        try:
            response = requests.post(url, headers=headers, data=json.dumps(data))
            print(f"发送验证码响应状态: {response.status_code}")
            print(f"发送验证码响应内容: {response.text}")

            if response.status_code == 200:
                # 假设响应中包含codeid,根据实际接口调整
                response_data = response.json()
                codeid = response_data.get('codeid', 'default_codeid')
                return True, codeid
            else:
                return False, None

        except Exception as e:
            print(f"发送验证码时出错: {e}")
            return False, None

    def get_emails_list(self, email, max_attempts=30, delay=2):
        """第二步1:查询邮件列表"""
        print(f"开始查询邮箱 {email} 的邮件列表...")

        for attempt in range(max_attempts):
            try:
                headers = {
                    'accept': 'application/json, text/javascript, */*; q=0.01',
                    'accept-language': 'zh-CN,zh;q=0.9',
                    'origin': 'https://www.nimail.cn'
                }

                # 构建请求数据
                data = {
                    'mail': email
                }

                response = requests.post(f"{self.nimail_api}/getmails", headers=headers, data=data)

                if response.status_code == 200:
                    emails_data = response.json()

                    # 根据实际API响应结构调整
                    if isinstance(emails_data, list) and len(emails_data) > 0:
                        print(f"第 {attempt + 1} 次查询: 找到 {len(emails_data)} 封邮件")
                        # 返回最新的邮件(假设列表是按时间顺序的,第一个或最后一个是最新的)
                        latest_email = emails_data[0]  # 根据实际情况调整索引
                        return latest_email
                    else:
                        print(f"第 {attempt + 1} 次查询: 未找到邮件,继续等待...")
                else:
                    print(f"第 {attempt + 1} 次查询: API响应异常,状态码: {response.status_code}")

            except Exception as e:
                print(f"第 {attempt + 1} 次查询时出错: {e}")

            time.sleep(delay)

        print("达到最大查询次数,未找到验证码邮件")
        return None

    def get_email_content(self, email, email_id):
        """第二步2:根据邮件ID获取邮件内容"""
        try:
            # 构建获取邮件内容的URL
            url = f"{self.nimail_api}/raw-html/{urllib.parse.quote(email)}/{email_id}"

            headers = {
                'accept': 'application/json, text/javascript, */*; q=0.01',
                'accept-language': 'zh-CN,zh;q=0.9',
                'origin': 'https://www.nimail.cn'
            }

            response = requests.get(url, headers=headers)

            if response.status_code == 200:
                print("成功获取邮件内容")
                return response.text
            else:
                print(f"获取邮件内容失败,状态码: {response.status_code}")
                return None

        except Exception as e:
            print(f"获取邮件内容时出错: {e}")
            return None

    def extract_verification_code(self, email_content):
        """第三步:从邮件内容中提取验证码"""
        if not email_content:
            return None

        # 使用正则表达式匹配验证码(6位数字)
        code_patterns = [
            r'\b\d{6}\b',  # 6位数字
            r'验证码.*?(\d{6})',  # 包含"验证码"的6位数字
            r'code.*?(\d{6})',  # 包含"code"的6位数字
            r'verification.*?(\d{6})',  # 包含"verification"的6位数字
            r'【.*?】.*?(\d{6})',  # 中文格式的验证码
            r'<strong>(\d{6})</strong>',  # HTML中的验证码
            r'<td[^>]*>(\d{6})</td>'  # 表格中的验证码
        ]

        for pattern in code_patterns:
            matches = re.search(pattern, email_content, re.IGNORECASE)
            if matches:
                verification_code = matches.group(1) if matches.groups() else matches.group(0)
                print(f"找到验证码: {verification_code}")
                return verification_code

        # 如果没有匹配到,尝试打印部分内容用于调试
        print("邮件内容预览(前500字符):", email_content[:500])
        print("未在邮件中找到验证码")
        return None

    def register_user(self, email, codeid, verification_code, password="123456"):
        """第四步:注册用户"""
        url = f"{self.base_url}/register"

        headers = {
            'Content-Type': 'text/plain'
        }

        data = {
            "username": email,
            "account": "",
            "password": password,
            "codeid": codeid,
            "code": int(verification_code)
        }

        try:
            response = requests.post(url, headers=headers, data=json.dumps(data))
            print(f"注册响应状态: {response.status_code}")
            print(f"注册响应内容: {response.text}")

            if response.status_code == 200:
                print("注册成功!")
                return True
            else:
                print("注册失败!")
                return False

        except Exception as e:
            print(f"注册时出错: {e}")
            return False

    def run_complete_verification(self):
        """执行完整的验证流程"""
        print("=== 开始邮箱验证流程 ===")

        # 第一步:生成随机邮箱并发送验证码
        email = self.generate_random_email()
        print(f"生成的随机邮箱: {email}")

        success, codeid = self.send_verification_code(email)
        if not success:
            print("发送验证码失败,终止流程")
            return False

        print(f"验证码发送成功,codeid: {codeid}")

        # 第二步1:查询邮件列表,获取最新邮件
        latest_email = self.get_emails_list(email)
        if not latest_email:
            print("未找到邮件,终止流程")
            return False

        # 获取邮件ID(根据实际API响应结构调整)
        email_id = latest_email.get('id')
        if not email_id:
            print("未找到邮件ID,终止流程")
            return False

        print(f"找到最新邮件,ID: {email_id}")

        # 第二步2:获取邮件内容
        email_content = self.get_email_content(email, email_id)
        if not email_content:
            print("获取邮件内容失败,终止流程")
            return False

        # 第三步:提取验证码
        verification_code = self.extract_verification_code(email_content)
        if not verification_code:
            print("未找到验证码,终止流程")
            return False

        # 第四步:注册用户
        success = self.register_user(email, codeid, verification_code)

        # 第五步:完成验证
        if success:
            print("=== 验证完成 ===")
        else:
            print("=== 验证失败 ===")

        return success

# 使用示例
if __name__ == "__main__":
    verifier = EmailVerification()

    # 执行一次完整的验证流程
    verifier.run_complete_verification()

    # 如果需要批量执行,可以使用循环
    # for i in range(5):  # 执行5次
    #     print(f"\n=== 第 {i+1} 次执行 ===")
    #     verifier.run_complete_verification()
    #     time.sleep(5)  # 每次执行间隔5秒
共收到 9 条回复 时间 点赞

666666666666666666666

优秀👍👍

我刚遇到这个问题,现在就看到这个文章,太好了,赶紧去试试

Cindylong 回复

有问题随时可以贴出来哦,互相学习一起进步

怎么获取验证码?

Cindylong 回复

你要拿到自己的邮件,然后用正则去匹配验证码部分。得看下邮件的原件,用程序去解析匹配。

Cindylong 回复

这不巧了吗,我最近刚写了一个获取邮件信息的方法,再用正则表达式或者其他方式提取一下验证码

from imapclient import IMAPClient
import email
from email.header import decode_header
from email.utils import parseaddr


def get_email(sender_email=None, folder="INBOX", subject_keyword=None,
              only_unread=False, mark_as_read=False, limit=None):
    """
    使用 IMAPClient 获取邮件,并设置 IMAP ID 信息
    """
    IMAP_SERVER = "XXXX"
    EMAIL_ACCOUNT = "XXXX"
    PASSWORD = "XXXX"

    result = []

    with IMAPClient(IMAP_SERVER, ssl=True) as server:
        # 登录
        server.login(EMAIL_ACCOUNT, PASSWORD)

        # 设置 IMAP ID 信息
        server.id_({
            "name": "myname",
            "version": "1.0.0",
            "vendor": "myclient",
            "support-email": "testmail@test.com"
        })

        # 列出文件夹(调试用)
        folders = server.list_folders()
        print("所有文件夹:")
        for i in folders:
            print(i)

        # 选择目标文件夹
        try:
            server.select_folder(folder)
        except Exception as e:
            print(f"无法打开文件夹: {folder}\n{e}")
            return []

        # 搜索条件
        criteria = []
        if only_unread:
            criteria.append("UNSEEN")
        else:
            criteria.append("ALL")
        if sender_email:
            criteria.append(f'FROM "{sender_email}"')

        # 搜索邮件
        messages = server.search(criteria)
        if not messages:
            print("没有找到符合条件的邮件")
            return []

        print(f"找到 {len(messages)} 封符合条件的邮件")

        count = 0
        for msgid, data in server.fetch(messages, ["RFC822"]).items():
            msg = email.message_from_bytes(data[b"RFC822"])

            # 解码主题
            subject, encoding = decode_header(msg["Subject"])[0]
            if isinstance(subject, bytes):
                subject = subject.decode(encoding or "utf-8", errors="ignore")

            if subject_keyword and subject_keyword not in subject:
                continue

            # 解码发件人
            raw_from = msg.get("From")
            name, addr = parseaddr(raw_from)
            decoded_name = decode_header(name)[0][0]
            if isinstance(decoded_name, bytes):
                decoded_name = decoded_name.decode("utf-8", errors="ignore")
            from_ = f"{decoded_name} <{addr}>"

            # 提取正文
            body = ""
            if msg.is_multipart():
                for part in msg.walk():
                    content_type = part.get_content_type()
                    content_disposition = str(part.get("Content-Disposition"))
                    if content_type == "text/plain" and "attachment" not in content_disposition:
                        charset = part.get_content_charset() or "utf-8"
                        try:
                            body = part.get_payload(decode=True).decode(charset, errors="ignore")
                        except Exception:
                            body = ""
                        break
            else:
                charset = msg.get_content_charset() or "utf-8"
                body = msg.get_payload(decode=True).decode(charset, errors="ignore")

            result.append({
                "subject": subject,
                "from": from_,
                "body": body
            })

            # 标记为已读
            if mark_as_read:
                server.set_flags(msgid, ["\\Seen"])

            count += 1
            if limit and count >= limit:
                break

    return result


# 测试:读取未读邮件
if __name__ == "__main__":
    emails = get_email(folder="INBOX", only_unread=True, mark_as_read=True limit=1)
    print("结果:", emails)

淘宝一个短信转发助手,然后转发的邮箱 从邮箱读取内容 正则提取😀 氪金就行 便宜的狠

上官一 回复

大佬太强了👏

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册