- 新增jd模块基础路由,整合app_store和payment子路由 - 实现苹果权益充值接口,支持苹果、携程及沃尔玛多个渠道 - 实现卡号密码查询接口,支持不同类别订单查询 - 新增短信认证相关接口,实现短信验证码发送及短信登录 - 新增商品管理接口,支持SKU详情查询及账号类下单功能 - 新增订单管理接口,实现订单删除功能 - 实现支付相关接口,增加刷新支付参数功能 - 定义完整请求及响应数据模型,确保接口数据规范 - 编写AppStoreSpider类,封装苹果应用内订单处理逻辑 - 引入多种代理池及请求重试机制,增强接口稳定性 - 添加详细日志记录,便于请求追踪与错误排查
330 lines
12 KiB
Python
330 lines
12 KiB
Python
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)
|