Files
kami_spider_monorepo/apps/jd/services/login.py
danial 6c768b6e7b feat(jd): 添加京东相关路由及苹果权益充值功能
- 新增jd模块基础路由,整合app_store和payment子路由
- 实现苹果权益充值接口,支持苹果、携程及沃尔玛多个渠道
- 实现卡号密码查询接口,支持不同类别订单查询
- 新增短信认证相关接口,实现短信验证码发送及短信登录
- 新增商品管理接口,支持SKU详情查询及账号类下单功能
- 新增订单管理接口,实现订单删除功能
- 实现支付相关接口,增加刷新支付参数功能
- 定义完整请求及响应数据模型,确保接口数据规范
- 编写AppStoreSpider类,封装苹果应用内订单处理逻辑
- 引入多种代理池及请求重试机制,增强接口稳定性
- 添加详细日志记录,便于请求追踪与错误排查
2025-11-03 19:35:39 +08:00

330 lines
12 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import base64
import json
import os
import platform
import ddddocr
import execjs
from curl_cffi import requests
from observability.logging import get_logger_with_trace
logger = get_logger_with_trace(__name__)
class LoginSpider:
def __init__(self, phone_):
self.phone = phone_
self.headers = {
"accept": "application/json, text/plain, */*",
"accept-language": "zh-CN,zh;q=0.9",
"cache-control": "no-cache",
"content-type": "application/x-www-form-urlencoded",
"origin": "https://plogin.m.jd.com",
"pragma": "no-cache",
"priority": "u=1, i",
"referer": "https://plogin.m.jd.com/login/login?appid=300&returnurl=https%3A%2F%2Fm.jd.com%2F&source=wq_passport",
"sec-ch-ua": '"Microsoft Edge";v="131", "Chromium";v="131", "Not_A Brand";v="24"',
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": '"Windows"',
"sec-fetch-dest": "empty",
"sec-fetch-mode": "cors",
"sec-fetch-site": "same-origin",
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36 Edg/131.0.0.0",
}
self.cookies = {}
self.current_os = platform.system()
self.js_path = None
self.node_modules_path = None
self.load_directory()
self.slide_ctw = self.init_verify_data_js()
self.user_ctw = self.init_user_encrypt_js()
self.h5st_ctw = self.init_h5st_js()
self.session = requests.Session()
self.ocr = ddddocr.DdddOcr()
self.eid = ""
self.fp = "26114226dc0c6ee7f1eaf3d4abb30cf2"
self.s_token = None
self.rsa_modulus = None
self.api_st = None
self.api_fp = None
self.img1 = None
self.img2 = None
self.vt = None
self.img = None
self.jd_risk_token_id = None
self.jcap_sid = None
self.ck = None
def load_directory(self):
if self.current_os == "Linux":
self.js_path = r"/app/js"
self.node_modules_path = r"/app/node_modules"
else:
self.js_path = "js"
self.node_modules_path = "./node_modules"
def do_execjs(self, path):
return execjs.compile(
open(path, encoding="utf8").read(), cwd=self.node_modules_path
)
def init_verify_data_js(self):
js_path = os.path.join(self.js_path, "modules.js")
return execjs.compile(
open(js_path, encoding="gbk", errors="ignore").read(),
cwd=self.node_modules_path,
)
def init_user_encrypt_js(self):
js_path = os.path.join(self.js_path, "user_encrypt.js")
return execjs.compile(
open(js_path, encoding="gbk", errors="ignore").read(),
cwd=self.node_modules_path,
)
def init_h5st_js(self):
js_path = os.path.join(self.js_path, "h5st-5.0.js")
return execjs.compile(
open(js_path, encoding="gbk", errors="ignore").read(),
cwd=self.node_modules_path,
)
def request_jd_risk_token_id(self):
response = self.session.get(
"https://payrisk.jd.com/m.html", cookies=self.cookies, headers=self.headers
)
return response.text
def build_jcapsid_data(self):
return self.user_ctw.call(
"init", self.phone, self.fp, self.jd_risk_token_id, self.s_token
)
def request_jcapsid(self):
url = "https://plogin.m.jd.com/cgi-bin/mm/jcapsid"
data = self.build_jcapsid_data()
response = self.session.post(url, headers=self.headers, data=data)
print(f"request_jcapsid返回{response.text}")
return response.json()
def request_new_login_entrance(self):
url = "https://plogin.m.jd.com/cgi-bin/mm/new_login_entrance"
params = {
"lang": "chs",
"returnurl": "https://my.m.jd.com/",
"risk_jd\\[eid\\]": self.eid,
"risk_jd\\[fp\\]": self.fp,
}
response = self.session.get(
url, headers=self.headers, cookies=self.cookies, params=params
)
print(response.text)
return response.json()
def get_jcap_sid(self):
jcapsid_res = self.request_jcapsid()
self.jcap_sid = jcapsid_res["jcap_sid"]
def get_login_entrance(self):
login_entrance_res = self.request_new_login_entrance()
self.s_token = login_entrance_res["s_token"]
self.rsa_modulus = login_entrance_res["rsa_modulus"]
def build_fp_data(self):
return self.slide_ctw.call("getFp", self.phone, self.jcap_sid)
def request_data_fp(self):
data = self.build_fp_data()
url = "https://jcap.m.jd.com/cgi-bin/api/fp"
response = self.session.post(url, headers=self.headers, data=data)
print(response.text)
return response.json()
def get_api_params(self):
fp_res = self.request_data_fp()
self.api_st = fp_res["st"]
self.api_fp = fp_res["fp"]
def build_captcha_data(self):
return self.slide_ctw.call("getImage", self.phone, self.jcap_sid, self.api_st)
def request_captcha(self):
url = "https://jcap.m.jd.com/cgi-bin/api/check"
data = self.build_captcha_data()
response = requests.post(url, headers=self.headers, data=data)
print(response.text)
return response.json()
def set_img(self, captcha_res):
self.img = captcha_res["img"]
img_json = json.loads(captcha_res["img"])
self.api_st = captcha_res["st"]
self.img1 = img_json["b1"]
self.img2 = img_json["b2"]
def get_captcha(self):
captcha_res = self.request_captcha()
if captcha_res.get("vt"):
self.vt = captcha_res["vt"]
self.api_st = captcha_res["st"]
return True
self.set_img(captcha_res)
return False
def verify_captcha(self):
bg = self.img1.replace("data:image/jpg;base64,", "")
background = base64.b64decode(bg)
fg = self.img2.replace("data:image/png;base64,", "")
target = base64.b64decode(fg)
result = self.ocr.slide_match(target, background, simple_target=True)
x = round(result["target"][0] * (290 / 275))
url = "https://jcap.m.jd.com/cgi-bin/api/check"
data = self.slide_ctw.call(
"verify", self.phone, self.jcap_sid, self.img, self.api_st, x
)
response = self.session.post(url, headers=self.headers, data=data)
print(response.text)
return response.json()
def check_captcha(self):
captcha_res = self.verify_captcha()
if captcha_res.get("vt"):
self.vt = captcha_res["vt"]
self.api_st = captcha_res["st"]
return True
self.set_img(captcha_res)
return False
def build_send_code_data(self):
return self.user_ctw.call(
"sendMsg",
self.phone,
self.vt,
self.jd_risk_token_id,
self.s_token,
self.rsa_modulus,
)
def send_code(self):
url = "https://plogin.m.jd.com/cgi-bin/mm/dosendlogincode"
data = self.build_send_code_data()
response = self.session.post(url, headers=self.headers, data=data)
print(response.text)
self.ck = self.get_cookie()
return response.json()
def get_h5st(self, code):
return self.h5st_ctw.call("getH5st", code, self.s_token)
def build_verify_data(self, phone_, code, s_token, jd_risk_token_id, rsa_modulus):
h5st = self.get_h5st(code)
return self.user_ctw.call(
"login", phone_, code, s_token, h5st, jd_risk_token_id, rsa_modulus
)
def get_cookie(self):
cookie_dict = self.session.cookies.get_dict()
return "; ".join([f"{key}={value}" for key, value in cookie_dict.items()]) + ";"
def request_sms_login(self, ck, code, s_token, jd_risk_token_id, rsa_modulus):
url = "https://plogin.m.jd.com/cgi-bin/mm/dosmslogin"
h5st = self.get_h5st(code)
data = self.user_ctw.call(
"login", phone, code, s_token, h5st, jd_risk_token_id, rsa_modulus
)
self.headers["cookie"] = ck
response = requests.post(url, headers=self.headers, data=data)
print(response.text)
self.ck = self.get_cookie()
return response.json()
def get_jd_risk_token_id(self):
self.jd_risk_token_id = (
self.request_jd_risk_token_id()
.split("var jd_risk_token_id = ")[1]
.strip(";")
.strip("'")
)
def get_code_res(self):
send_res = self.send_code()
if send_res.get("err_code") == 0:
return 100, {
"ck": self.ck,
"s_token": self.s_token,
"jd_risk_token_id": self.jd_risk_token_id,
"rsa_modulus": self.rsa_modulus,
}
else:
return 101, {
"ck": "",
"s_token": "",
"jd_risk_token_id": "",
"rsa_modulus": "",
}
@staticmethod
def my_response(status_code, data):
return {"code": status_code, "data": data, "msg": "请求成功"}
def run_get_ck(self, ck, code, s_token, jd_risk_token_id, rsa_modulus):
login_res = self.request_sms_login(
ck, code, s_token, jd_risk_token_id, rsa_modulus
)
if login_res.get("err_code") == 0:
data = {"ck": self.ck}
return self.my_response(status_code=100, data=data)
else:
data = {"ck": ""}
return self.my_response(status_code=101, data=data)
def run_send_code(self):
try:
# 获取risk_token_id
self.get_jd_risk_token_id()
# 获取登录所需初始sid
self.get_login_entrance()
self.get_jcap_sid()
# 获取验证码
self.get_api_params()
captcha_status = self.get_captcha()
if not captcha_status:
print("出现滑块验证")
# 验证验证码
check_status = self.check_captcha()
if check_status:
print("滑块验证通过,发送验证码")
status_code, data = self.get_code_res()
return self.my_response(status_code, data)
return None
else:
print("没有出现滑块验证,直接发送验证码")
status_code, data = self.get_code_res()
return self.my_response(status_code, data)
except:
return self.my_response(status_code=111, data={})
if __name__ == "__main__":
phone_ = "13071534209"
res = LoginSpider(
phone_=phone_,
).run_send_code()
print(res)
res = LoginSpider(
phone_=phone_,
).run_get_ck(
ck="jcap_dvzw_fp=7Hq5_QuYK92sDlclFxhJ8m28JaYU1hDYJ-U-gC59LmJRPuo3ERCbiMGzn5vdc3WcTX0ndWGFZNOs3BDenMr7lw==; guid=a5bb2dc650ab23a2cf5cb58afef19b3f35adba9613421b4cba79aa59ea891803; lang=chs; lsid=503346699194megeguncgrf6gvwb9tdujp2md5p27pkt1737276375048; lstoken=vvwcnbi4;",
code="315572",
s_token="vvwcnbi4",
jd_risk_token_id="SH6IDCXBUWZNAV3SJPRGIXLVNHGLZR6AYHJ3244TGU5Z35SFJU76TAQ3OMNTXE7XA3SBRMPVTZJVW",
rsa_modulus="B03744DE9EAB28F6FA6B9C8FB1873CF57D42A2B6D382B79B276C2079A42B24C11D641EA642CF62485A632AE244DE6DE05A92A20EEFE8B6C7743F09FCE0BF78E6D614C115CDAEC2F4825F82E06770A2599D69BBADBE678DD25F2E5B9E2D0E3E15BEB749B436860872D30676794D3C3E8C37B71372DE52F223917FA730EC21F047",
)
print(res)