| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196 |
- """药九九浏览器自动化登录 — Playwright 填表登录,成功后 cookie 写入 accounts_platform"""
- import json
- import time
- import random
- from playwright.sync_api import sync_playwright, TimeoutError as PlaywrightTimeoutError
- from commons.Logger import get_spider_logger
- from commons.conn_mysql import MySQLPoolOn2
- logger = get_spider_logger("yaojiujiu_login")
- LOGIN_URL = "https://www.yyjzt.com/login"
- PLATFORM_ID = 7
- class YaoJiuJiuLogin:
- """Playwright 浏览器自动化登录药九九"""
- def __init__(self):
- self.db = MySQLPoolOn2()
- # ==================== 账号获取 ====================
- def get_account(self):
- sql = """SELECT `id`, `phone`, `password` FROM `accounts_platform`
- WHERE `platform`=%s AND `status`=1
- ORDER BY `cookie_timestamp` ASC LIMIT 1"""
- rows = self.db.select_data(sql, (PLATFORM_ID,))
- return rows[0] if rows else None
- # ==================== 浏览器登录 ====================
- def login(self, phone, password, headless=True):
- """
- 打开浏览器 → 填表 → 点击登录 → 提取 cookie → 关闭浏览器
- 成功返回 yjj-token cookie 值,失败返回 None
- """
- with sync_playwright() as p:
- browser = p.chromium.launch(
- headless=headless,
- channel="chrome",
- args=[
- "--disable-blink-features=AutomationControlled",
- "--no-sandbox",
- ],
- )
- context = browser.new_context(
- locale="zh-CN",
- timezone_id="Asia/Shanghai",
- viewport={"width": 1600, "height": 900},
- )
- page = context.new_page()
- try:
- # 1. 打开登录页
- page.goto(LOGIN_URL, timeout=30000)
- page.wait_for_load_state("networkidle")
- # 2. 确保在"账户密码登录"tab(id="tab-first")
- pwd_tab = page.locator("#tab-first")
- if pwd_tab.count() > 0:
- pwd_tab.click()
- page.wait_for_timeout(500)
- # 3. 填账号
- username_input = page.locator("input[name='username']")
- username_input.wait_for(state="visible", timeout=10000)
- username_input.click()
- username_input.fill("")
- # 模拟逐字输入
- for ch in str(phone):
- username_input.type(ch, delay=random.randint(60, 180))
- page.wait_for_timeout(random.randint(30, 80))
- logger.info("已输入账号")
- # 4. 填密码
- password_input = page.locator("input[name='password']")
- password_input.click()
- password_input.fill("")
- for ch in str(password):
- password_input.type(ch, delay=random.randint(60, 180))
- page.wait_for_timeout(random.randint(30, 80))
- logger.info("已输入密码")
- # 5. 点登录
- login_btn = page.locator("button:has-text('登录')").first
- if login_btn.locator("..").first:
- # 等待按钮可用(Element UI 的验证需要输入完整后才解除 disabled)
- page.wait_for_timeout(500)
- login_btn.click()
- logger.info("已点击登录")
- # 6. 等待登录结果 — 页面跳转或报错
- page.wait_for_timeout(3000)
- try:
- page.wait_for_load_state("networkidle", timeout=15000)
- except Exception:
- pass
- # 7. 检查登录状态
- if "login" in page.url.lower():
- # 检查是否有错误提示
- err = page.locator(".el-message--error, .el-form-item__error, [class*='error']").first
- if err.count() > 0:
- logger.error("登录失败: %s", err.inner_text(timeout=3000))
- else:
- logger.error("登录失败,仍在登录页")
- return None
- # 8. 提取 cookie
- cookies = context.cookies()
- browser.close()
- # 找 yjj-token
- token = ""
- for c in cookies:
- if c["name"] == "yjj-token":
- token = c["value"].replace("ssr_", "")
- break
- if not token:
- logger.error("登录后未找到 yjj-token cookie")
- return None
- logger.info("登录成功, token=%s...", token[:20])
- return {"token": token, "cookies": cookies}
- except PlaywrightTimeoutError as e:
- logger.error("登录超时: %s", e)
- return None
- except Exception as e:
- logger.exception("登录异常: %s", e)
- return None
- finally:
- try:
- browser.close()
- except Exception:
- pass
- # ==================== Cookie 入库 ====================
- def save_cookies(self, phone, cookies):
- """保存全套 cookie 到 accounts_platform,供快照模式注入"""
- # 提取关键 cookie
- cookie_map = {}
- for c in cookies:
- cookie_map[c["name"]] = c["value"]
- # Token 单独存,兼容 API 模式
- token = cookie_map.get("yjj-token", "").replace("ssr_", "")
- cookie_map["Token"] = token
- cookie_str = json.dumps(cookie_map, ensure_ascii=False)
- now_ts = int(time.time())
- sql = """UPDATE `accounts_platform`
- SET `cookie_str`=%s, `cookie_timestamp`=%s
- WHERE `phone`=%s AND `platform`=%s"""
- affected = self.db.execute(sql, (cookie_str, now_ts, str(phone), PLATFORM_ID))
- if affected:
- logger.info("Cookie 已写入 accounts_platform (含 %s 个 key), phone=%s", len(cookie_map), phone)
- else:
- logger.warning("Cookie 写入失败, phone=%s", phone)
- def save_token(self, phone, token):
- """兼容旧调用"""
- self.save_cookies(phone, [{"name": "yjj-token", "value": "ssr_" + str(token)}])
- # ==================== 入口 ====================
- def run(self, headless=True):
- """执行登录:读数据库账号 → 浏览器登录 → 入库,返回 token"""
- acc = self.get_account()
- if not acc:
- logger.error("无可用药九九账号")
- return None
- phone = acc["phone"]
- password = acc["password"]
- # 先看缓存
- cached = acc.get("cookie_str") or ""
- if cached:
- try:
- cd = json.loads(cached)
- t = cd.get("Token") or cd.get("token")
- if t:
- logger.info("使用缓存 token")
- return t
- except json.JSONDecodeError:
- pass
- # 浏览器登录
- result = self.login(phone, password, headless=headless)
- if result:
- self.save_cookies(phone, result["cookies"])
- return result["token"]
- return None
- if __name__ == "__main__":
- t = YaoJiuJiuLogin().run(headless=False)
- print("token:", t)
|