login_yaojiujiu.py 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. """药九九浏览器自动化登录 — Playwright 填表登录,成功后 cookie 写入 accounts_platform"""
  2. import json
  3. import time
  4. import random
  5. from playwright.sync_api import sync_playwright, TimeoutError as PlaywrightTimeoutError
  6. from commons.Logger import get_spider_logger
  7. from commons.conn_mysql import MySQLPoolOn2
  8. logger = get_spider_logger("yaojiujiu_login")
  9. LOGIN_URL = "https://www.yyjzt.com/login"
  10. PLATFORM_ID = 7
  11. class YaoJiuJiuLogin:
  12. """Playwright 浏览器自动化登录药九九"""
  13. def __init__(self):
  14. self.db = MySQLPoolOn2()
  15. # ==================== 账号获取 ====================
  16. def get_account(self):
  17. sql = """SELECT `id`, `phone`, `password` FROM `accounts_platform`
  18. WHERE `platform`=%s AND `status`=1
  19. ORDER BY `cookie_timestamp` ASC LIMIT 1"""
  20. rows = self.db.select_data(sql, (PLATFORM_ID,))
  21. return rows[0] if rows else None
  22. # ==================== 浏览器登录 ====================
  23. def login(self, phone, password, headless=True):
  24. """
  25. 打开浏览器 → 填表 → 点击登录 → 提取 cookie → 关闭浏览器
  26. 成功返回 yjj-token cookie 值,失败返回 None
  27. """
  28. with sync_playwright() as p:
  29. browser = p.chromium.launch(
  30. headless=headless,
  31. channel="chrome",
  32. args=[
  33. "--disable-blink-features=AutomationControlled",
  34. "--no-sandbox",
  35. ],
  36. )
  37. context = browser.new_context(
  38. locale="zh-CN",
  39. timezone_id="Asia/Shanghai",
  40. viewport={"width": 1600, "height": 900},
  41. )
  42. page = context.new_page()
  43. try:
  44. # 1. 打开登录页
  45. page.goto(LOGIN_URL, timeout=30000)
  46. page.wait_for_load_state("networkidle")
  47. # 2. 确保在"账户密码登录"tab(id="tab-first")
  48. pwd_tab = page.locator("#tab-first")
  49. if pwd_tab.count() > 0:
  50. pwd_tab.click()
  51. page.wait_for_timeout(500)
  52. # 3. 填账号
  53. username_input = page.locator("input[name='username']")
  54. username_input.wait_for(state="visible", timeout=10000)
  55. username_input.click()
  56. username_input.fill("")
  57. # 模拟逐字输入
  58. for ch in str(phone):
  59. username_input.type(ch, delay=random.randint(60, 180))
  60. page.wait_for_timeout(random.randint(30, 80))
  61. logger.info("已输入账号")
  62. # 4. 填密码
  63. password_input = page.locator("input[name='password']")
  64. password_input.click()
  65. password_input.fill("")
  66. for ch in str(password):
  67. password_input.type(ch, delay=random.randint(60, 180))
  68. page.wait_for_timeout(random.randint(30, 80))
  69. logger.info("已输入密码")
  70. # 5. 点登录
  71. login_btn = page.locator("button:has-text('登录')").first
  72. if login_btn.locator("..").first:
  73. # 等待按钮可用(Element UI 的验证需要输入完整后才解除 disabled)
  74. page.wait_for_timeout(500)
  75. login_btn.click()
  76. logger.info("已点击登录")
  77. # 6. 等待登录结果 — 页面跳转或报错
  78. page.wait_for_timeout(3000)
  79. try:
  80. page.wait_for_load_state("networkidle", timeout=15000)
  81. except Exception:
  82. pass
  83. # 7. 检查登录状态
  84. if "login" in page.url.lower():
  85. # 检查是否有错误提示
  86. err = page.locator(".el-message--error, .el-form-item__error, [class*='error']").first
  87. if err.count() > 0:
  88. logger.error("登录失败: %s", err.inner_text(timeout=3000))
  89. else:
  90. logger.error("登录失败,仍在登录页")
  91. return None
  92. # 8. 提取 cookie
  93. cookies = context.cookies()
  94. browser.close()
  95. # 找 yjj-token
  96. token = ""
  97. for c in cookies:
  98. if c["name"] == "yjj-token":
  99. token = c["value"].replace("ssr_", "")
  100. break
  101. if not token:
  102. logger.error("登录后未找到 yjj-token cookie")
  103. return None
  104. logger.info("登录成功, token=%s...", token[:20])
  105. return {"token": token, "cookies": cookies}
  106. except PlaywrightTimeoutError as e:
  107. logger.error("登录超时: %s", e)
  108. return None
  109. except Exception as e:
  110. logger.exception("登录异常: %s", e)
  111. return None
  112. finally:
  113. try:
  114. browser.close()
  115. except Exception:
  116. pass
  117. # ==================== Cookie 入库 ====================
  118. def save_cookies(self, phone, cookies):
  119. """保存全套 cookie 到 accounts_platform,供快照模式注入"""
  120. # 提取关键 cookie
  121. cookie_map = {}
  122. for c in cookies:
  123. cookie_map[c["name"]] = c["value"]
  124. # Token 单独存,兼容 API 模式
  125. token = cookie_map.get("yjj-token", "").replace("ssr_", "")
  126. cookie_map["Token"] = token
  127. cookie_str = json.dumps(cookie_map, ensure_ascii=False)
  128. now_ts = int(time.time())
  129. sql = """UPDATE `accounts_platform`
  130. SET `cookie_str`=%s, `cookie_timestamp`=%s
  131. WHERE `phone`=%s AND `platform`=%s"""
  132. affected = self.db.execute(sql, (cookie_str, now_ts, str(phone), PLATFORM_ID))
  133. if affected:
  134. logger.info("Cookie 已写入 accounts_platform (含 %s 个 key), phone=%s", len(cookie_map), phone)
  135. else:
  136. logger.warning("Cookie 写入失败, phone=%s", phone)
  137. def save_token(self, phone, token):
  138. """兼容旧调用"""
  139. self.save_cookies(phone, [{"name": "yjj-token", "value": "ssr_" + str(token)}])
  140. # ==================== 入口 ====================
  141. def run(self, headless=True):
  142. """执行登录:读数据库账号 → 浏览器登录 → 入库,返回 token"""
  143. acc = self.get_account()
  144. if not acc:
  145. logger.error("无可用药九九账号")
  146. return None
  147. phone = acc["phone"]
  148. password = acc["password"]
  149. # 先看缓存
  150. cached = acc.get("cookie_str") or ""
  151. if cached:
  152. try:
  153. cd = json.loads(cached)
  154. t = cd.get("Token") or cd.get("token")
  155. if t:
  156. logger.info("使用缓存 token")
  157. return t
  158. except json.JSONDecodeError:
  159. pass
  160. # 浏览器登录
  161. result = self.login(phone, password, headless=headless)
  162. if result:
  163. self.save_cookies(phone, result["cookies"])
  164. return result["token"]
  165. return None
  166. if __name__ == "__main__":
  167. t = YaoJiuJiuLogin().run(headless=False)
  168. print("token:", t)