Commit 88e50876 by baiquan

add files

parents
from loguru import logger
from celery.result import AsyncResult
from fastapi import FastAPI, APIRouter
from fastapi import status
from pydantic import BaseModel
from celery_app import celery_app
from errors import *
app = FastAPI(title="doudian数据同步平台")
sync_router = APIRouter(prefix="/sync", tags=["同步接口"])
class SyncShopRequest(BaseModel):
container_name: str = "抖店"
class SyncShopInfoRequest(BaseModel):
browser_id: str
listen_data: dict
class CreateTemplateRequest(BaseModel):
cookies: dict
template_params: dict
class DoudianLoginRequest(BaseModel):
account: str
password: str
@app.post(
"/sync_shop",
status_code=status.HTTP_202_ACCEPTED,
summary="异步触发店铺同步",
description="提交店铺同步任务到后台异步执行,返回任务ID用于查询状态",
)
async def async_sync_shop(req: SyncShopRequest):
"""异步执行店铺同步"""
task = celery_app.send_task('sync_shop', kwargs=req.dict())
return {"task_id": task.id, "status_url": f"/tasks/{task.id}"}
@app.post(
"/sync_shop_info",
status_code=status.HTTP_202_ACCEPTED,
summary="异步触发店铺信息同步",
description="提交店铺信息同步任务到后台异步执行,返回任务ID用于查询状态",
)
async def async_sync_shop_info(req: SyncShopInfoRequest):
"""异步执行店铺信息同步"""
task = celery_app.send_task('sync_shop_info', kwargs=req.dict())
return {"task_id": task.id, "status_url": f"/tasks/{task.id}"}
# 新增同步接口
@sync_router.post(
"/sync_shop",
status_code=status.HTTP_200_OK,
summary="同步执行店铺同步"
)
def sync_shop(req: SyncShopRequest):
"""同步执行店铺同步(阻塞式)"""
try:
task = celery_app.send_task('sync_shop', kwargs=req.dict())
result = task.get(timeout=60) # 60秒超时
return result
except TimeoutError:
return {
'code': 504,
'msg': '请求超时',
'data': None,
'error_type': 'TimeoutError'
}
@sync_router.post(
"/sync_shop_info",
status_code=status.HTTP_200_OK,
summary="同步执行店铺信息同步",
)
def sync_shop_info(req: SyncShopInfoRequest):
"""同步执行店铺信息同步(阻塞式)"""
try:
task = celery_app.send_task('sync_shop_info', kwargs=req.dict())
result = task.get(timeout=180) # 浏览器操作较慢,设置3分钟超时
logger.info(f"任务结果: {result}")
return result
except Exception as e:
logger.error(f"任务执行失败: {str(e)}")
return {
'code': 504,
'msg': '请求超时',
'data': None,
'error_type': 'TimeoutError'
}
@sync_router.post(
"/create_template",
status_code=status.HTTP_200_OK,
summary="创建运费模板",
)
def sync_create_template(req: CreateTemplateRequest):
try:
task = celery_app.send_task('sync_create_template', kwargs=req.dict())
result = task.get(timeout=60)
logger.info(f"任务结果: {result}")
return result
except Exception as e:
logger.error(f"任务执行失败: {str(e)}")
return {
'code': 504,
'msg': '请求超时',
'data': None,
'error_type': 'TimeoutError'
}
@sync_router.post(
"/login",
status_code=status.HTTP_200_OK,
summary="抖店登录",
)
def doudian_login(req: DoudianLoginRequest):
try:
task = celery_app.send_task('doudian_login', kwargs=req.dict())
result = task.get(timeout=60)
logger.info(f"任务结果: {result}")
return result
except Exception as e:
logger.error(f"任务执行失败: {str(e)}")
return {
'code': 504,
'msg': '请求超时',
'data': None,
'error_type': 'TimeoutError'
}
# 注册路由
app.include_router(sync_router)
# 原有的任务状态查询接口保持不变
@app.get(
"/tasks/{task_id}",
summary="查询任务状态",
description="根据任务ID查询异步任务执行状态",
)
def get_task_result(task_id: str):
try:
if not task_id:
raise ParamsError(msg="任务ID不能为空")
task_result = AsyncResult(task_id, app=celery_app)
if not task_result:
raise NotFoundError(msg="任务不存在")
return {
"code": 200,
"msg": "success",
"data": {
"task_id": task_id,
"status": task_result.status,
"result": task_result.result if task_result.ready() else None,
}
}
except Exception as e:
logger.error(f"查询任务状态失败: {str(e)}")
raise AppError
\ No newline at end of file
from celery import Celery
from config import settings
celery_app = Celery(
'doudian_tasks',
broker=settings.CELERY_BROKER_URL,
backend=settings.CELERY_RESULT_BACKEND,
include=['task_worker']
)
celery_app.conf.update(
task_serializer='json',
result_serializer='json', # 确保结果序列化
accept_content=['json'],
result_accept_content=['json'],
task_track_started=True,
result_extended=True, # 显示完整结果信息
result_expires=3600 # 1小时过期
)
import json
import random
import time
from hashlib import md5
from urllib.parse import urlencode
import requests
def encryptParamsId(login_subject_uid, user_identity_id):
e = {
"login_subject_uid": login_subject_uid,
"user_identity_id": user_identity_id
}
r = ["login_subject_uid", "user_identity_id"]
s = 0
for key in r:
if key in e and e[key] is not None:
s |= 1
e[key] = encrypt(e[key])
e['mix_mode'] = s
return e
def get_ms_token(randomlength=172):
"""
根据传入长度产生随机字符串
"""
random_str = ''
base_str = 'ABCDEFGHIGKLMNOPQRSTUVWXYZabcdefghigklmnopqrstuvwxyz0123456789='
length = len(base_str) - 1
for _ in range(randomlength):
random_str += base_str[random.randint(0, length)]
return random_str
def get_access_key(fp_id, app_key, device_id):
params = f'{fp_id}{app_key}{device_id}' + 'f8a69f1719916z'
obj = md5()
obj.update(params.encode("utf-8"))
access_key = obj.hexdigest()
return access_key
def update_callback_cookies(cookies, headers, redirect_url):
callback_res = requests.get(redirect_url, cookies=cookies, headers=headers, allow_redirects=False)
return update_cookies(callback_res.cookies, cookies)
def get_account_sdk_source_info():
data = {
"hardwareConcurrency": 8,
"webdriver": False,
"chromedriver": False,
"shelldriver": False,
"plugins": 5,
"permissions": [{"name": "notifications", "state": "denied"}],
"innerHeight": 900,
"innerWidth": 1440,
"outerHeight": 1040,
"outerWidth": 1920,
"stoargeStatus": {
"indexedDB": {
"idb": "object",
"open": "function",
"indexedDB": "object",
"IDBKeyRange": "function",
"openDatabase": "undefined",
"isSafari": False,
"hasFetch": False
},
"localStorage": {"isSupportLStorage": True, "size": 904853, "write": True},
"storageQuotaStatus": {"usage": 917313, "quota": 64425765273, "isPrivate": False}
},
"webgl": {
"vendor": "Google Inc. (NVIDIA)",
"renderer": "ANGLE (NVIDIA, NVIDIA GeForce GTX 550 Ti (0x00001244) Direct3D11 vs_5_0 ps_5_0, D3D11)"
},
"notificationPermission": "denied",
"performance": {
"timeOrigin": 1678901234567,
"usedJSHeapSize": "unsupported",
"navigationTiming": {
"decodedBodySize": 12345,
"entryType": "navigation",
"initiatorType": "navigation",
"name": "https://fxg.jinritemai.com/login/common",
"renderBlockingStatus": "non-blocking",
"serverTiming": "inner,bd-gf-microfe,bd-gf-file-total,bd-gf-total,bd-hdd-sched,bd-hdd-exec,cdn-cache,edge,origin",
"guleStart": "none",
"guleDuration": "none"
}
},
"request_host": "fxg.jinritemai.com",
"request_pathname": "/login/common",
"browser": {}
}
return encrypt(json.dumps(data, separators=(',', ':')))
def update_cookies(new_cookies, old_cookies):
if new_cookies:
for k, v in new_cookies.items():
if v and k:
old_cookies[k] = v
return old_cookies
def get_check_send():
hex_bytes = ["%02x" % i for i in range(256)]
node_default = bytes([139, 163, 84, 84, 53, 114])
clock_seq_default = 8384
def format_uuid(byte_arr, offset=0):
parts = [
hex_bytes[byte_arr[offset]], hex_bytes[byte_arr[offset+1]],
hex_bytes[byte_arr[offset+2]], hex_bytes[byte_arr[offset+3]], '-',
hex_bytes[byte_arr[offset+4]], hex_bytes[byte_arr[offset+5]], '-',
hex_bytes[byte_arr[offset+6]], hex_bytes[byte_arr[offset+7]], '-',
hex_bytes[byte_arr[offset+8]], hex_bytes[byte_arr[offset+9]], '-',
hex_bytes[byte_arr[offset+10]], hex_bytes[byte_arr[offset+11]],
hex_bytes[byte_arr[offset+12]], hex_bytes[byte_arr[offset+13]],
hex_bytes[byte_arr[offset+14]], hex_bytes[byte_arr[offset+15]]]
return ''.join(parts)
e = {}
node = e.get('node', node_default)
clock_seq = e.get('clockseq', clock_seq_default)
current_time = int(time.time() * 1000)
time_low = (current_time & 0xffffffff)
time_mid = ((current_time >> 32) & 0xffff)
time_hi = ((current_time >> 48) & 0x0fff) | 0x1000
uuid_bytes = bytearray(16)
uuid_bytes[0:4] = time_low.to_bytes(4, 'big')
uuid_bytes[4:6] = time_mid.to_bytes(2, 'big')
uuid_bytes[6:8] = time_hi.to_bytes(2, 'big')
uuid_bytes[8:9] = ((clock_seq >> 8) | 0x80).to_bytes(1, 'big')
uuid_bytes[9:10] = (clock_seq & 0xff).to_bytes(1, 'big')
uuid_bytes[10:16] = node[:6]
return format_uuid(uuid_bytes)
def getFp():
chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
t = len(chars)
n = str(int(time.time())).zfill(36)
r = [None] * 36 # 初始化一个长度为36的列表
r[8], r[13], r[18], r[23] = "_", "_", "_", "_"
r[14] = "4"
for i in range(36):
if r[i] is None:
o = random.randint(0, t - 1)
r[i] = chars[o if i != 19 else (o & 3 | 8)]
return "verify_" + n + "_" + "".join(r)
def encrypt(e):
if e is None:
return ""
t = []
for char in e:
code = ord(char)
if code <= 127:
t.append(code)
elif 128 <= code <= 2047:
t.append((192 | (code >> 6)) & 0xFF)
t.append((128 | (code & 63)) & 0xFF)
else:
t.append((224 | (code >> 12)) & 0xFF)
t.append((128 | ((code >> 6) & 63)) & 0xFF)
t.append((128 | (code & 63)) & 0xFF)
n = []
for num in t:
n.append(f"{(5 ^ num):02x}")
return "".join(n)
def encryptParams(account, password):
e = {
'account': account,
'captcha_key': "",
'fp': getFp(),
'password': password,
'redirect_sso_to_login': False,
'service': "https://fxg.jinritemai.com/login/common"
}
r = ["account", "password"]
s = 0
for key in r:
if key in e and e[key] is not None:
s |= 1
e[key] = encrypt(e[key])
e['mix_mode'] = s
return e
from dynaconf import Dynaconf
import os
settings = Dynaconf(
envvar_prefix="DYNACONF",
settings_files=['settings.toml', '.secrets.toml'],
environments=True,
)
os.environ["ENV_FOR_DYNACONF"] = "production"
import base64
import random
import time
from urllib.parse import urlencode
import ddddocr
import requests
from playwright.sync_api import sync_playwright, Page
def get_img_bytes_by_htmltag_selector(page: Page, selector: str) -> bytes:
"""
根据浏览器标签id获取图片bytes 数据
:param page: page对象
:param selector: css选择器 img#captcha-verify_img_slide 表示找img标签id=captcha-verify_img_slide的
:return:
"""
img_bytes = b''
if "img" in selector:
page.wait_for_selector(selector)
img_element = page.query_selector(selector)
img_url = img_element.get_attribute('src')
img_bytes = requests.get(img_url).content
elif "canvas" in selector:
page.wait_for_selector(selector)
canvas = page.query_selector(selector)
base64_data: str = page.evaluate('(canvas) => canvas.toDataURL("image/jpg")', canvas).replace(
'data:image/png;base64,', '')
img_bytes = base64.b64decode(base64_data)
return img_bytes
def get_distance_by_ddddocr(slide_img_bytes, target_img_bytes) -> int:
"""
通过ddddocr识别到目标图案的坐标
:param slide_img_bytes: 小滑块图片字节
:param target_img_bytes: 目标背景图片字节
:return: 目标图案坐标x轴
"""
det = ddddocr.DdddOcr(det=False, ocr=False, show_ad=False)
res = det.slide_match(slide_img_bytes, target_img_bytes, simple_target=True)
target_x = res["target"][0]
return target_x
def mouse_verify(fp, verify_data):
params = {
'fp' : fp,
'verify_data' : verify_data
}
url = f"https://rmc.bytedance.com/verifycenter/captcha/v2?from=iframe&env=%7B%22screen%22%3A%7B%22w%22%3A1920%2C%22h%22%3A1080%7D%2C%22browser%22%3A%7B%22w%22%3A1920%2C%22h%22%3A1040%7D%2C%22page%22%3A%7B%22w%22%3A812%2C%22h%22%3A928%7D%2C%22document%22%3A%7B%22width%22%3A812%7D%2C%22product_host%22%3A%22fxg.jinritemai.com%22%2C%22vc_version%22%3A%221.0.0.219%22%2C%22maskTime%22%3A1742456870440%2C%22h5_check_version%22%3A%224.0.5%22%7D&aid=4272&scene_level=p2&app_name=acweb&host=%2F%2Fverify.zijieapi.com&lang=zh&theme=%7B%22half_cn_OkBtnBgColor%22%3A%22%231966FF%22%7D&{urlencode(params)}"
with sync_playwright() as p:
browser = p.chromium.launch(headless=False, args=['--disable-blink-features=AutomationControlled'])
page = browser.new_page()
page.goto(url)
page.wait_for_timeout(2000) # 滑块加载太慢了等待5秒防止异常
# ddddocr识别
slide_img_bytes = get_img_bytes_by_htmltag_selector(page, "img#captcha-verify_img_slide")
target_img_bytes = get_img_bytes_by_htmltag_selector(page, "img#captcha_verify_image")
x = get_distance_by_ddddocr(slide_img_bytes, target_img_bytes)
# 计算实际x轴位置
selector = "div.captcha-slider-btn"
slide_element = page.wait_for_selector(selector)
slide_element_pos = slide_element.bounding_box()
width = slide_element_pos['width']
height = slide_element_pos['height']
x = x / (width / height) # 计算实际位置,x/宽高比
# 开始滑动
mouse = page.mouse
mouse.move(slide_element_pos['x'], slide_element_pos['y'])
page.wait_for_timeout(200)
mouse.down()
mouse.move(slide_element_pos['x'] + x, slide_element_pos['y'], steps=random.randint(10, 15))
page.wait_for_timeout(200)
mouse.up()
time.sleep(1)
browser.close()
# mouse_verify("verify_m8h1txdd_fee7e695_2e77_e4e7_a5d5_3dc49ed62419", "{\"code\":\"10000\",\"from\":\"shark_admin\",\"type\":\"verify\",\"version\":\"1\",\"region\":\"cn\",\"subtype\":\"slide\",\"ui_type\":\"\",\"detail\":\"0ytVI9I7IYsrvaxAlaa0yZ*IOCR6H*NwX36FoZ-MGtURmqugDdrrkGP2lbJ-YXQelgidUL*iIP8y8C2F6XaaupyCtG0Hqot0kQVGraXuY7TjX5Oc*piLqebcSc3aUT7JWNbfo9qQuXNDZR-EQyb-rIfcYyleJl1zVJIOGgEZV9jaSaQO7YUBg*0bHGPtmmO2tGQsLmIKLeOGQ8wnjWWY4Bz4WnoP1v7N7gMH4oIgY1kv6CwJ5DQKmKs3P2BCnFSP*vWidImF*9zz2tn5M0GC05wtMeBjVKHGyppYMpIBu6ev9UkIN07DCpXs69Nr2m3yxp-5hJBJ16GssyrJPlhNPe51qD--j0X90X1pd2ggB4TouGbFk1sak4Ld94nOsszkZDOIxzFaGYuCGbxqi7xNQJGyqs3Iu401jvTizlj3jv-0N5hHVGEE7UvkRyp*E8Y6HeM.\",\"verify_event\":\"tt_sso_account_login\",\"fp\":\"verify_m8h1txdd_fee7e695_2e77_e4e7_a5d5_3dc49ed62419\",\"server_sdk_env\":\"{\\\"idc\\\":\\\"lf\\\",\\\"region\\\":\\\"CN\\\",\\\"server_type\\\":\\\"passport\\\"}\",\"log_id\":\"2025032015474966300A687F9A35597A36\",\"is_assist_mobile\":false,\"is_complex_sms\":false,\"identity_action\":\"\",\"identity_scene\":\"\",\"verify_scene\":\"passport\",\"login_status\":0,\"aid\":0}")
\ No newline at end of file
class AppError(Exception):
"""基础异常类"""
def __init__(self, code=500, msg='系统错误', data=None):
self.code = code
self.msg = msg
self.data = data
class HubAPIError(AppError):
"""Hub接口异常"""
def __init__(self, msg='Hub服务异常', data=None):
super().__init__(code=503, msg=msg, data=data)
class NotFoundError(AppError):
"""资源未找到异常"""
def __init__(self, msg='资源不存在', data=None):
super().__init__(code=404, msg=msg, data=data)
class ParamsError(AppError):
"""参数校验异常"""
def __init__(self, msg='参数错误', data=None):
super().__init__(code=400, msg=msg, data=data)
import asyncio
import json
import subprocess
from errors import HubAPIError
from curl_cffi import requests
from curl_cffi.requests import HttpMethod
from loguru import logger
from config import settings
DOMAIN = settings.HUB_DOMAIN
DEFAULT_HEADER = {
"Content-Type": "application/json",
"Accept": "application/json",
}
async def sendRequest(method: HttpMethod, url: str, json_data: dict = None, **kwargs) -> dict:
"""
发送请求
"""
timeout = kwargs.get('timeout', 60)
response = requests.request(method, f"{DOMAIN}{url}", json=json_data, headers=DEFAULT_HEADER, timeout=timeout).text
# 判断是否可以转换为json
try:
response = json.loads(response)
except json.JSONDecodeError:
return response
# 判断是否有code字段
if 'code' in response:
if response['code'] != 0:
raise HubAPIError(msg=response['msg'])
if response['code'] != 0:
raise HubAPIError(msg=response['msg'])
if settings.DEBUG:
logger.debug(f'hub接口返回值:{json.dumps(response)}')
return response
async def openExe():
"""
启动软件
"""
# --line_setting int API线路设置, 0默认线路 1国内线路 2海外线路, 默认-1跟随客户端线路设置 (default -1)
command = (
f'{settings.HUB_EXE_PATH} --server_mode=http --threads=20 --line_setting=1 '
f'--http_port=6873 --app_id={settings.HUB_APP_ID} --group_code={settings.HUB_GROUP_ID} --app_secret={settings.HUB_APP_SECRET}')
subprocess.run(command)
async def envList(json_data: dict = None):
"""
环境列表
"""
url = f'/api/v1/env/list'
return await sendRequest('POST', url, json_data)
async def exportCookie(json_data: dict = None):
"""
导出cookie
"""
url = '/api/v1/env/export-cookie'
return await sendRequest('POST', url, json_data)
async def openBrowser(browser_id: str = "", timeout: int = 120):
"""
启动浏览器
"""
url = '/api/v1/browser/start'
data = {
'containerCode': browser_id,
# 是否只读模式(不保存cookie)
'isWebDriverReadOnlyMode': False,
"args": [
"--start-maximized", # 全屏
"--disable-popup-blocking", # 允许自动打开弹出式窗口
"--download.prompt_for_download=false",
]
}
return await sendRequest("POST", url=url, json_data=data, **{'timeout': timeout})
async def closeBrowser(browser_id: str = ""):
"""
关闭浏览器
:param browser_id:
:return:
"""
url = '/api/v1/browser/stop'
data = {
'containerCode': browser_id,
}
browser_status = await checkBrowserStatus(browser_id)
browser_status_data = browser_status.get('data', {})
if browser_status_data:
containers = browser_status_data.get('containers', {})
if containers:
if (containers[0]['status'] == 1 or containers[0]['status'] == 0) and containers[0]['containerCode'] == browser_id:
return await sendRequest("POST", url=url, json_data=data)
async def checkBrowserStatus(browser_id: str = ""):
"""
查看浏览器状态
:param browser_id:
:return:
"""
url = '/api/v1/browser/all-browser-status'
data = {
'containerCode': [browser_id],
}
return await sendRequest("POST", url=url, json_data=data)
if __name__ == '__main__':
# data = asyncio.run(envList({"containerName": "希音"}))
data = asyncio.run(openExe())
# data = data['data']
# print(data)
import logging
from urllib.parse import urlencode
import requests
from common import get_account_sdk_source_info, get_ms_token, encryptParams, encryptParamsId, update_cookies, \
update_callback_cookies
from dy_verify import mouse_verify
headers = {
'Accept': 'application/json, text/plain, */*',
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',
'Connection': 'keep-alive',
'Content-Type': 'application/x-www-form-urlencoded',
'Origin': 'https://fxg.jinritemai.com',
'Referer': 'https://fxg.jinritemai.com/',
'Sec-Fetch-Dest': 'empty',
'Sec-Fetch-Mode': 'cors',
'Sec-Fetch-Site': 'same-site',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36 Edg/133.0.0.0',
'X-Requested-With': 'XMLHttpRequest',
'sec-ch-ua': '"Not(A:Brand";v="99", "Microsoft Edge";v="133", "Chromium";v="133"',
}
def get_callback_cookies(login_cookies, headers, encrypt_params):
account_sdk_source_info = get_account_sdk_source_info()
check_login_params = {
'fp': encrypt_params['fp'],
'aid': '4272',
'language': 'zh',
'account_sdk_source': 'sso',
'service': '',
'subject_aid': '4966',
'need_ticket': 'false',
'account_sdk_source_info': account_sdk_source_info,
'msToken': get_ms_token(),
}
x_bogus_url = 'https://doudian-sso.jinritemai.com/aff/check_login/' + "?" + urlencode(check_login_params)
check_login_url = x_bogus_url + f'&signature=_02B4Z6wo00001k1CzegAAIDCemYJt5a7r6JNQslAAPSgmx8MO5UrYu5YW6SCdzCrIIqaFJbM6j62JJU3g5yarKiSx7EgoKdBA92pBjYFVyjR9yIxwEceUkyEGviysE59DRUIWFyx3Akm0kWcbc'
check_login_res = requests.get(check_login_url, cookies=login_cookies, headers=headers)
login_subject_uid = check_login_res.json()['login_list'][0]['login_subject_uid']
user_identity_id = check_login_res.json()['login_list'][0]['user_identity_list'][0]['user_identity_id_str']
encrypt_obj = encryptParamsId(login_subject_uid, user_identity_id)
ticket_data = {
'fp': encrypt_params['fp'],
'aid': '4272',
'language': 'zh',
'account_sdk_source': 'web',
'service': encrypt_params['service'],
'subject_aid': '4966',
'mix_mode': '1',
'login_subject_uid': encrypt_obj['login_subject_uid'],
'user_identity_id': encrypt_obj['user_identity_id'],
}
ticket_url = f"https://doudian-sso.jinritemai.com/aff/subject/login/?subject_aid=4966&fp={encrypt_params['fp']}&aid=4272&language=zh&account_sdk_source=web&account_sdk_source_info={account_sdk_source_info}&msToken={get_ms_token()}"
ticket_res = requests.post(ticket_url, cookies=login_cookies, headers=headers, data=ticket_data)
cookies = update_cookies(ticket_res.cookies, login_cookies)
ticket = ticket_res.json()['redirect_url'].split('ticket=')[-1]
redirect_params = {
'next': 'https://fxg.jinritemai.com',
'ticket': ticket,
'aid': '4272',
'subject_aid': '4966',
'login_member': '1',
'_lid': '312003629857',
}
redirect_res = requests.get('https://fxg.jinritemai.com/index/login', params=redirect_params, cookies=cookies,
headers=headers,
allow_redirects=False)
redirect_url = redirect_res.headers.get('Location')
params = {
'login_source': 'doudian_pc_web',
'subject_aid': '4966',
'bus_child_type': '0',
'entry_source': '0',
'ecom_login_extra': '',
}
cookies = update_callback_cookies(cookies, headers, redirect_url)
callback_res = requests.get('https://fxg.jinritemai.com/ecomauth/loginv1/callback', params=params, cookies=cookies,
headers=headers, verify=False)
logging.info(f"-------- get_callback_cookies: cookies获取成功!--------")
return update_cookies(callback_res.cookies, cookies)
def login(account, password):
encrypt_params = encryptParams(account, password)
login_params = {
'fp': encrypt_params['fp'],
'aid': '4272',
'language': 'zh',
'account_sdk_source': 'web',
'account_sdk_source_info': get_account_sdk_source_info(),
'msToken': get_ms_token(),
}
login_url = 'https://doudian-sso.jinritemai.com/account_login/v2/' + "?" + urlencode(login_params)
data = {
'fp': encrypt_params['fp'],
'aid': '4272',
'language': 'zh',
'account_sdk_source': 'web',
'mix_mode': encrypt_params['mix_mode'],
'service': encrypt_params['service'],
'account': encrypt_params['account'],
'password': encrypt_params['password'],
'captcha_key': encrypt_params['captcha_key'],
'redirect_sso_to_login': encrypt_params['redirect_sso_to_login'],
}
login_res = requests.post(login_url, headers=headers, data=data, verify=False)
if login_res.json()['error_code'] == 2046:
logging.info(f"-------- login: 登录失败!--------")
logging.info(f"-------- login: {login_res.json()['description']}--------")
return None
if login_res.json()['error_code'] == 3:
logging.info(f"-------- login: 登录失败!--------")
logging.info(f"-------- login: {login_res.json()['description']}--------")
return None
if login_res.json()['description'] == '滑动滑块进行验证':
logging.info(f"-------- login_verify: 正在处理滑块!--------")
verify_center_decision_conf = login_res.json()['verify_center_decision_conf']
count = 0
while True:
count += 1
try:
mouse_verify(encrypt_params['fp'], verify_center_decision_conf)
except:
continue
login_res = requests.post(login_url, headers=headers, data=data, verify=False)
if login_res.json()['description'] != '滑动滑块进行验证':
logging.info(f"-------- login_verify: 滑块处理成功!--------")
break
if count == 5:
logging.info(f"-------- login_verify: 处理滑块失败!--------")
logging.info(f"-------- login: 登录失败!--------")
return None
logging.info(f"-------- login: 登录成功!--------")
return dict(get_callback_cookies(login_res.cookies, headers, encrypt_params))
This diff is collapsed. Click to expand it.
[development]
HUB_APP_ID = "password"
[production]
DEBUG = true
SHEIN_COOKIE_DOMAINS = "fxg.jinritemai.com,.fxg.jinritemai.com,.compass.jinritemai.com,.buyin.jinritemai.com,.jinritemai.com"
HUB_DOMAIN = "http://127.0.0.1:6873"
HUB_EXE_PATH = "D://hubstudio//Hubstudio.exe"
HUB_GROUP_ID = "doudian"
HUB_APP_SECRET = "password"
CELERY_BROKER_URL = "redis://localhost:6379/0"
CELERY_RESULT_BACKEND = "redis://localhost:6379/1"
# 系统域名
SYSTEM_DOMAINS = "https://sheinss.top"
SYSTEM_APP_NAME = "admin"
SYSTEM_TASK_QUEUE = "task-queue"
SYSTEM_TASK_QUEUE_NUMBER = 10
SYSTEM_MIN_TASK_QUEUE_NUMBER = 5
SYSTEM_PROCESS_NUMBER = 5
# sqlite3
DB_NAME = "shein_order.db"
\ No newline at end of file
# 启动Celery Worker
celery -A celery_app worker -l info -P gevent
# 启动API服务
uvicorn api:app --host 0.0.0.0 --reload
from celery import shared_task
from login import login
from errors import *
from hub_ import *
from main import syncShop, syncShopInfo, createTemplate
@shared_task(name='sync_shop')
def execute_sync_shop(container_name: str):
"""执行店铺同步任务"""
try:
# 保持异步执行上下文
result = asyncio.run(syncShop(container_name))
return {
'code': 200,
'msg': 'success',
'data': result,
'error_type': ''
}
except AppError as e:
logger.error(f'店铺同步异常:{e}')
return {
'code': e.code,
'msg': e.msg,
'data': e.data,
'error_type': type(e).__name__
}
except Exception as e:
logger.error(f'店铺同步异常:{e}')
return {
'code': 500,
'msg':f'店铺同步异常:{e}',
'data': None,
'error_type': 'InternalError'
}
@shared_task(name='sync_shop_info')
def execute_sync_shop_info(browser_id: str, listen_data: list):
"""执行店铺信息同步任务"""
try:
parameter = {
'browser_id': browser_id,
'listen_data': listen_data
}
result = asyncio.run(syncShopInfo(parameter))
return {
'code': 200,
'msg': 'success',
'data': result,
'error_type': ''
}
except AppError as e:
logger.error(f'同步店铺信息异常:{e}')
return {
'code': e.code,
'msg': e.msg,
'data': e.data,
'error_type': type(e).__name__
}
except Exception as e:
logger.error(f'同步店铺信息异常:{e}')
return {
'code': 500,
'msg': f'同步店铺信息异常:{e}',
'data': None,
'error_type': 'InternalError'
}
finally:
asyncio.run(closeBrowser(browser_id))
@shared_task(name='sync_create_template')
def execute_sync_create_template(cookies: dict, template_params: dict):
"""创建运费模板"""
try:
result = asyncio.run(createTemplate(cookies, template_params))
return {
'code': 200,
'msg': 'success',
'data': result,
'error_type': ''
}
except AppError as e:
logger.error(f'创建运费模板异常:{e}')
return {
'code': e.code,
'msg': e.msg,
'data': e.data,
'error_type': type(e).__name__
}
except Exception as e:
logger.error(f'创建运费模板:{e}')
return {
'code': 500,
'msg': f'创建运费模板:{e}',
'data': None,
'error_type': 'InternalError'
}
@shared_task(name='doudian_login')
def execute_doudian_login(account: str, password: str):
"""登录"""
try:
result = login(account, password)
return {
'code': 200,
'msg': 'success',
'data': result,
'error_type': ''
}
except AppError as e:
logger.error(f'登录异常:{e}')
return {
'code': e.code,
'msg': e.msg,
'data': e.data,
'error_type': type(e).__name__
}
except Exception as e:
logger.error(f'登录异常:{e}')
return {
'code': 500,
'msg': f'登录异常:{e}',
'data': None,
'error_type': 'InternalError'
}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment