OfpayGrab.py 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542
  1. # -*- coding: utf-8 -*-
  2. import os
  3. import csv
  4. import requests
  5. import json
  6. from datetime import datetime
  7. # from http.cookies import SimpleCookie
  8. from cachetools import TTLCache
  9. import pickle
  10. import multiprocessing
  11. from multiprocessing import Manager, Pool
  12. def read_csv(filename='results.csv'):
  13. data = []
  14. try:
  15. with open(filename, mode='r', encoding='utf-8') as csvfile:
  16. reader = csv.DictReader(csvfile);
  17. for row in reader:
  18. data.append(row);
  19. except IOError as e:
  20. print(f"read_csv error occurred: {e}");
  21. # print(data);
  22. return data;
  23. def read_json(filename='results.json'):
  24. data = None;
  25. try:
  26. with open(filename, 'r', encoding='utf-8') as file:
  27. data = json.load(file);
  28. except IOError as e:
  29. print(f"read_json error occurred: {e}");
  30. # print(data);
  31. return data;
  32. g_shared_cache = None;
  33. class SharedCache:
  34. cache_file_path = 'elife.cache.pickle';
  35. def __init__(self):
  36. self.init();
  37. def init(self):
  38. cache = None;
  39. cache_file_path = SharedCache.cache_file_path;
  40. if os.path.exists(cache_file_path):
  41. # 从文件中加载缓存对象
  42. with open(cache_file_path, 'rb') as file:
  43. cache = pickle.load(file);
  44. else:
  45. cache = TTLCache(maxsize=100, ttl=60*60*24);
  46. # 将缓存对象持久化到文件
  47. with open(cache_file_path, 'wb') as file:
  48. pickle.dump(cache, file);
  49. self.cache = cache;
  50. def save(self):
  51. cache_file_path = SharedCache.cache_file_path;
  52. with open(cache_file_path, 'wb') as file:
  53. pickle.dump(self.cache, file);
  54. def set(self, key, value):
  55. self.cache[key] = value;
  56. def get(self, key):
  57. return self.cache.get(key);
  58. class OfpayGrabber:
  59. CheckBuyRepeatEnable = True;
  60. FastModeEnable = True;
  61. CheckStockEnable = True;
  62. def __init__(self, accout_data, activities_data):
  63. self.award_want_discount_dict = None;
  64. self.activities_data = activities_data;
  65. self.enable = True if accout_data['enable'] == 1 else False;
  66. self.accout_data = accout_data;
  67. self.host = 'market-web.ofpay.com';
  68. self.market_id = accout_data['market_id'];
  69. self.event_visitor_id = accout_data['event_visitor_id'];
  70. self.accout_id = accout_data['account'];
  71. uuid = accout_data['uuid'];
  72. user_agent = accout_data['user_agent'];
  73. authorization = accout_data['authorization'];
  74. self.cookies = eval(accout_data['cookies']);
  75. cookies_str = '; '.join([f'{key}={value}' for key, value in self.cookies.items()]);
  76. self.headers = {
  77. 'Host': self.host,
  78. 'UUID': uuid,
  79. 'Accept': '*/*',
  80. 'Sec-Fetch-Site': 'same-origin',
  81. 'Origin': 'https://market-web.ofpay.com',
  82. 'Accept-Language': 'zh-CN,zh-Hans;q=0.9',
  83. 'Accept-Encoding': 'gzip, deflate, br',
  84. 'Sec-Fetch-Mode': 'cors',
  85. 'Content-Type': 'application/json; charset=utf-8',
  86. 'Connection': 'keep-alive',
  87. 'User-Agent': user_agent,
  88. 'Accept-Language': 'zh-CN,zh-Hans;q=0.9',
  89. 'Authorization': authorization,
  90. 'Sec-Fetch-Dest': 'empty',
  91. 'Referer': 'https://market-web.ofpay.com/h5/union/standard/interactiveIGoChoose/index',
  92. 'Cookie': cookies_str,
  93. };
  94. def start(self):
  95. ret_code = self.check_refresh_token();
  96. print(f"账号[{self.accout_id}]token数据状态:{ret_code}");
  97. if ret_code < 0:
  98. print(f"账号[{self.accout_id}]token已失效");
  99. return;
  100. if ret_code == 1:
  101. self.sync_new_token();
  102. cate_items = self.get_market_items_from_cache();
  103. if not cate_items:
  104. cate_items = self.get_market_items_from_svr(self.market_id, self.event_visitor_id);
  105. if not cate_items:
  106. return;
  107. all_buy_list = self.get_will_market_buy_list_all();
  108. will_cate_count = len(all_buy_list);
  109. all_ret_list = [];
  110. sort_num = 0;
  111. for activity_data in cate_items:
  112. if 'sortNum' in activity_data:
  113. sort_num = activity_data['sortNum'];
  114. if sort_num >= will_cate_count:
  115. continue;
  116. buy_list = all_buy_list[sort_num];
  117. print(sort_num, buy_list);
  118. buy_ret_list = self.check_to_buy_all(buy_list, activity_data);
  119. all_ret_list.extend(buy_ret_list);
  120. sort_num += 1;
  121. if len(all_ret_list):
  122. g_shared_cache.save();
  123. return all_ret_list;
  124. def check_refresh_token(self, force_refresh=False):
  125. expire_time_str = self.accout_data['expire_time'];
  126. expire_dt = datetime.strptime(expire_time_str, "%Y-%m-%d %H:%M:%S")
  127. now_dt = datetime.now();
  128. is_valid = False;
  129. if now_dt < expire_dt:
  130. is_valid = True;
  131. if not force_refresh and is_valid:
  132. return 0;
  133. login_params = self.accout_data['login_params'];
  134. url = f'https://{self.host}/h5/union/interactiveIGoChoose/index?loginParams={login_params}';
  135. response = self.get_request(url);
  136. # print(response.content);
  137. cookie_dict = dict(response.cookies);
  138. authorization = None;
  139. for key in cookie_dict:
  140. self.cookies[key] = cookie_dict[key];
  141. if key == 'unionToken_interactiveIGoChoose':
  142. authorization = self.cookies[key];
  143. if authorization:
  144. self.headers['Authorization'] = authorization;
  145. cookies_str = '; '.join([f'{key}={value}' for key, value in self.cookies.items()]);
  146. self.headers['Cookie'] = cookies_str;
  147. return 1;
  148. return -1;
  149. def sync_new_token(self):
  150. print('sync_new_token');
  151. def get_will_market_buy_list_all(self):
  152. def_val = '星巴克|霸王茶姬|百果园|京东E卡|滴滴快车';
  153. name_arr = [];
  154. name_str = self.accout_data['will_buy_list'] if self.accout_data['will_buy_list'] else def_val;
  155. if len(name_str):
  156. segments = name_str.strip().split('|');
  157. name_arr = [];
  158. for vstr in segments:
  159. if not len(vstr):
  160. name_arr.append(None);
  161. else:
  162. vlist = vstr.strip().split(',');
  163. name_arr.append(vlist);
  164. else:
  165. name_arr = [];
  166. return name_arr;
  167. def get_award_expected_discount(self, price, prize_name):
  168. if not self.award_want_discount_dict:
  169. self.award_want_discount_dict = {};
  170. def_val = '星巴克#20.80|霸王茶姬#10.80|百果园#10.80|京东E卡#10.80|滴滴快车#10.80';
  171. discount_str = self.accout_data['will_discount'] if self.accout_data['will_discount'] else def_val;
  172. if len(discount_str):
  173. segments = discount_str.strip().split('|');
  174. for vstr in segments:
  175. if len(vstr):
  176. vlist = vstr.strip().split('#');
  177. key = vlist[0];
  178. if vlist[1]:
  179. price_val = float(vlist[1].strip());
  180. self.award_want_discount_dict[key] = price_val;
  181. if self.award_want_discount_dict.get(prize_name) != None:
  182. return self.award_want_discount_dict[prize_name];
  183. return price - 8.8;
  184. def get_market_items_from_cache(self):
  185. if not self.activities_data:
  186. self.activities_data = read_json('elife_activities_data.json');
  187. return self.activities_data;
  188. def save_market_items_cache(self, market_id, event_visitor_id, data):
  189. with open('elife_activities_data.json', 'w', newline='', encoding='utf-8') as f:
  190. json_str = json.dumps(data, cls=DecimalEncoder, ensure_ascii=False);
  191. f.write(json_str);
  192. def get_market_items_from_svr(self, market_id, event_visitor_id):
  193. params = {
  194. 'marketId': market_id,
  195. 'eventVisitorId': event_visitor_id
  196. };
  197. url = ('https://%s/h5/union/api/interactiveIGoChoose/indexConfigRebuild' % (self.host));
  198. print("请求市场商品列表数据");
  199. response = self.get_request(url, params);
  200. if response.status_code == 200:
  201. try:
  202. json_str = response.content;
  203. json_str = json_str.decode('utf-8');
  204. params = json.loads(json_str);
  205. if params.get('code') == 'success':
  206. print("请求市场商品列表数据成功");
  207. # self.save_market_items_cache(params['data']);
  208. return params['data'];
  209. else:
  210. print(f"请求市场商品列表数据成功,响应:{json_str}");
  211. except Exception as e:
  212. print("请求市场商品列表发生错误");
  213. print(e);
  214. finally:
  215. pass
  216. else:
  217. print("请求市场商品列表发生错误");
  218. return None;
  219. def check_to_buy_all(self, buy_list, activity_data):
  220. activity_id = activity_data['activityId'];
  221. sub_activity_id = activity_data['subActivityId'] if 'subActivityId' in activity_data else None;
  222. sub_login_type = activity_data['subLoginType'];
  223. award_list = activity_data['awardList'];
  224. ret_list = [];
  225. for i in range(len(buy_list)):
  226. one_ret = self.check_to_buy_one(activity_id, sub_activity_id, sub_login_type, buy_list[i], award_list);
  227. if one_ret:
  228. ret_list.append(one_ret);
  229. return ret_list;
  230. def check_to_buy_one(self, activity_id, sub_activity_id, sub_login_type, item_name, award_list):
  231. one_ret = None;
  232. check_buy_repeat_key = f"lkOfPayBuyItemKey#{item_name}#{self.accout_id}";
  233. now_string = datetime.now().strftime('%Y-%m-%d %H:%M:%S');
  234. if OfpayGrabber.CheckBuyRepeatEnable:
  235. last_buy_succ_date = g_shared_cache.get(check_buy_repeat_key);
  236. if now_string == last_buy_succ_date:
  237. print(f"商品[{item_name}]今日已抢购成功过,跳过~~");
  238. return one_ret;
  239. if OfpayGrabber.FastModeEnable:
  240. for award_data in award_list:
  241. if item_name in award_data['prizeName']:
  242. print(f"开始尝试抢购-{award_data['prizeName']}{award_data['prizeDesc']},价格:{award_data['price']},库存:{award_data['remainStock']}");
  243. if OfpayGrabber.CheckStockEnable:
  244. if award_data['remainStock'] > 0:
  245. one_ret = self.item_buy_fast(activity_id, sub_activity_id, sub_login_type, award_data);
  246. if one_ret:
  247. # 抢购成功
  248. g_shared_cache.set(check_buy_repeat_key, now_string);
  249. else:
  250. print("库存不足,跳过~");
  251. else:
  252. one_ret = self.item_buy_fast(activity_id, sub_activity_id, sub_login_type, award_data);
  253. if one_ret:
  254. # 抢购成功
  255. g_shared_cache.set(check_buy_repeat_key, now_string);
  256. break;
  257. else:
  258. for award_data in award_list:
  259. if item_name in award_data['prizeName']:
  260. print(f"开始尝试抢购-{award_data['prizeName']}{award_data['prizeDesc']},价格:{award_data['price']},库存:{award_data['remainStock']}");
  261. if award_data['remainStock'] > 0:
  262. one_ret = self.item_buy_normal(activity_id, sub_activity_id, sub_login_type, award_data);
  263. if one_ret:
  264. # 抢购成功
  265. g_shared_cache.set(check_buy_repeat_key, now_string);
  266. else:
  267. print("库存不足,跳过~");
  268. break;
  269. return one_ret;
  270. def item_buy_fast(self, activity_id, sub_activity_id, sub_login_type, award_data):
  271. print("###item_buy_fast");
  272. activity_id = award_data['activityId']
  273. # 假设 ofpay_account_phone 和 event_visitor_id 在这段代码之外被定义
  274. # 或者作为参数传递给这个函数
  275. event_visitor_id = self.event_visitor_id;
  276. game_account = self.accout_id;
  277. third_info = json.loads(award_data['thirdInfo']);
  278. award_id = award_data['awardId'];
  279. pay_info = self.get_pay_info(activity_id, award_id, '', '', game_account, event_visitor_id);
  280. if pay_info:
  281. if 'detailId' in pay_info:
  282. pay_ret = self.pay(activity_id, event_visitor_id, pay_info['detailId']);
  283. if pay_ret:
  284. return award_data;
  285. return None;
  286. def get_pay_info(self, activity_id, award_id, goods_id, invitation_code, game_account, event_visitor_id):
  287. url = f'https://{self.host}/h5/union/api/draw/interactiveIGoChoose/{activity_id}?awardId={award_id}&goodsId={goods_id}&invitationCode={invitation_code}&gameAccount={game_account}&eventVisitorId={event_visitor_id}';
  288. print("请求商品预支付数据");
  289. response = self.get_request(url);
  290. if response.status_code == 200:
  291. try:
  292. json_str = response.content;
  293. json_str = json_str.decode('utf-8');
  294. params = json.loads(json_str);
  295. if params.get('code') == '0':
  296. print("请求商品预支付数据成功");
  297. print(json_str);
  298. return params;
  299. else:
  300. # 19=存在待支付订单
  301. print(f"请求商品预支付数据失败,响应:{json_str}")
  302. except Exception as e:
  303. print("请求商品预支付数据发生错误");
  304. print(e);
  305. finally:
  306. pass
  307. else:
  308. print("请求商品预支付数据发生错误");
  309. return None;
  310. def pay(self, activity_id, event_visitor_id, detail_id):
  311. account_phone = self.accout_id;
  312. post_data = {
  313. "detailId": detail_id,
  314. "rechargeAccount": account_phone,
  315. "account": account_phone,
  316. "appVersion": appVersion,
  317. };
  318. url = f"https://{self.host}/h5/api/mobile/activity/pay/{activity_id}?eventVisitorId={event_visitor_id}";
  319. body = json.dumps(post_data);
  320. response = self.post_request(url, body);
  321. if response.status_code == 200:
  322. try:
  323. json_str = response.content;
  324. json_str = json_str.decode('utf-8');
  325. params = json.loads(json_str);
  326. if params.get('code') == 'success':
  327. print("请求下单成功");
  328. print(json_str);
  329. return params;
  330. else:
  331. print(f"请求下单失败,响应:{json_str}");
  332. except Exception as e:
  333. print("请求下单发生错误");
  334. print(e);
  335. finally:
  336. pass
  337. else:
  338. print("请求下单发生错误");
  339. return None;
  340. def check_pick_item(self, prize_name, award_list):
  341. for award_data in award_list:
  342. one_prize_name = award_data['prizeName'];
  343. if '忽略' not in one_prize_name and prize_name in one_prize_name:
  344. return award_data;
  345. return None;
  346. def item_buy_normal(self, activity_id, sub_activity_id, sub_login_type, award_data):
  347. print("###item_buy_normal");
  348. activity_id = award_data["activityId"];
  349. prize_name = award_data["prizeName"];
  350. market_id = self.market_id;
  351. event_visitor_id = self.event_visitor_id;
  352. act_data_list = self.get_activity_items(market_id, activity_id, event_visitor_id);
  353. the_act_data = self.get_activity_data(activity_id, event_visitor_id);
  354. des_info = self.get_des_decode_info(activity_id, event_visitor_id);
  355. cate_act_type = sub_login_type;
  356. if des_info:
  357. if des_info["code"] != '0':
  358. # 5=已享这周首单优惠 7=人数过多稍后重试
  359. cate_act_type = 'subChoose'
  360. for act_data in act_data_list:
  361. act_type = act_data["type"]
  362. if act_type == cate_act_type:
  363. award_list = act_data["awardList"]
  364. new_award_item = self.check_pick_item(prize_name, award_list);
  365. if new_award_item:
  366. award_data = new_award_item;
  367. break;
  368. game_account = self.accout_id;
  369. third_info = json.loads(award_data["thirdInfo"]);
  370. award_id = award_data["awardId"];
  371. award_price = float(award_data["price"]);
  372. award_face_value = float(third_info["faceValue"]);
  373. award_data["faceValue"] = award_face_value;
  374. discount_price = self.get_award_expected_discount(award_face_value, prize_name);
  375. print(f"商品{prize_name}(面值:{award_face_value})\n匹配的最终价格:{award_price}\n预设折扣价格:{discount_price}#{award_id}");
  376. if award_price <= discount_price:
  377. activity_id = award_data["activityId"];
  378. pay_info = self.get_pay_info(activity_id, award_id, '', '', game_account, event_visitor_id);
  379. if pay_info:
  380. if "detailId" in pay_info:
  381. pay_ret = self.pay(activity_id, event_visitor_id, pay_info["detailId"]);
  382. if pay_ret:
  383. return award_data;
  384. else:
  385. print(f"未到预设折扣价格{discount_price},跳过~");
  386. return None;
  387. def get_activity_items(self, market_id, activity_id, event_visitor_id):
  388. url = f"https://{self.host}/h5/union/interactiveIGoChoose/marketIndexRebuild?marketId={market_id}&activityId={activity_id}&eventVisitorId={event_visitor_id}";
  389. print("请求活动商品列表数据");
  390. response = self.get_request(url);
  391. if response.status_code == 200:
  392. try:
  393. json_str = response.content;
  394. json_str = json_str.decode('utf-8');
  395. params = json.loads(json_str);
  396. if params.get('code') == 'success':
  397. print("请求活动商品列表数据成功");
  398. return params['data'];
  399. else:
  400. print(f"请求活动商品列表数据失败,响应:{json_str}");
  401. except Exception as e:
  402. print("请求活动商品列表发生错误");
  403. print(e);
  404. finally:
  405. pass
  406. else:
  407. print("请求活动商品列表发生错误");
  408. return None;
  409. def get_activity_data(self, activity_id, event_visitor_id):
  410. url = f"https://{self.host}/h5/api/mobile/activity/data?activityNo={activity_id}&eventVisitorId={event_visitor_id}";
  411. print("请求活动状态数据");
  412. response = self.get_request(url);
  413. if response.status_code == 200:
  414. try:
  415. json_str = response.content;
  416. json_str = json_str.decode('utf-8');
  417. params = json.loads(json_str);
  418. if params.get('code') == 'success':
  419. print("请求活动状态数据成功");
  420. return params['data'];
  421. else:
  422. print(f"请求活动状态数据失败,响应:{json_str}");
  423. except Exception as e:
  424. print("请求活动状态发生错误");
  425. print(e);
  426. finally:
  427. pass
  428. else:
  429. print("请求活动状态发生错误");
  430. return None;
  431. def get_des_decode_info(self, activity_id, event_visitor_id):
  432. url = f"https://{self.host}/h5/union/api/interactiveIGoChoose/getDesDecodeInfo?activityNo={activity_id}&eventVisitorId={event_visitor_id}";
  433. print("请求活动描述数据");
  434. response = self.get_request(url);
  435. if response.status_code == 200:
  436. try:
  437. json_str = response.content;
  438. json_str = json_str.decode('utf-8');
  439. params = json.loads(json_str);
  440. print("请求活动描述数据成功");
  441. print(json_str);
  442. return params;
  443. except Exception as e:
  444. print("请求活动描述发生错误");
  445. print(e);
  446. finally:
  447. pass
  448. else:
  449. print("请求活动描述发生错误");
  450. return None;
  451. def get_request(self, url, params=None):
  452. response = requests.get(url, headers=self.headers, params=params, cookies=self.cookies);
  453. return response;
  454. def post_request(self, url, data):
  455. response = requests.post(url, data=data, headers=self.headers, cookies=self.cookies);
  456. return response;
  457. def init_data_pre_excute():
  458. print('init_shared_data');
  459. def thread_worker_func(index, shared_namespace, account_info, activities_data):
  460. print(f'thread_worker_func#{index}');
  461. global g_shared_cache;
  462. g_shared_cache = shared_namespace.g_shared_cache;
  463. print('########账号[%s]开始抢券工作########' % account_info['account']);
  464. grabber = OfpayGrabber(account_info, activities_data);
  465. results = grabber.start();
  466. print('########################################');
  467. return f"账号[{account_info['account']}]任务完成";
  468. def main():
  469. global g_shared_cache;
  470. g_shared_cache = SharedCache();
  471. g_shared_cache.set('test', 10);
  472. cpu_threads = multiprocessing.cpu_count();
  473. print(f"CPU支持 {cpu_threads} 个线程/核心");
  474. activities_data = read_json('elife_activities_data.json');
  475. accout_data = read_csv('elife_accout_data.csv');
  476. if cpu_threads > 1:
  477. # 创建进程池
  478. manager = Manager();
  479. # 创建共享命名空间
  480. shared_namespace = manager.Namespace();
  481. shared_namespace.g_shared_cache = g_shared_cache;
  482. pool = multiprocessing.Pool(cpu_threads, initializer=init_data_pre_excute);
  483. results = [];
  484. index = 0;
  485. for item in accout_data:
  486. result = pool.apply_async(thread_worker_func,(index, shared_namespace, item, activities_data,));
  487. results.append(result);
  488. index += 1;
  489. # 关闭进程池,不再接受新的任务
  490. pool.close();
  491. # 等待所有任务完成
  492. pool.join();
  493. # for result in results:
  494. # print(result.get());
  495. else:
  496. for item in accout_data:
  497. print('########账号[%s]开始抢券工作########' % item['account']);
  498. grabber = OfpayGrabber(item, activities_data);
  499. grabber.start();
  500. print('########################################');
  501. if __name__ == "__main__":
  502. main();