代码拉取完成,页面将自动刷新
同步操作将从 web阿哲/WPH抢购 强制同步,此操作会覆盖自 Fork 仓库以来所做的任何修改,且无法恢复!!!
确定后同步将在后台操作,完成时将刷新页面,请耐心等待。
import time
import math
import random
import os
import pickle
import json
import requests
from requests.utils import dict_from_cookiejar
from requests.cookies import RequestsCookieJar
from bs4 import BeautifulSoup
from exception import AsstException
from log import logger
from util import (
DEFAULT_USER_AGENT,
get_random_useragent,
save_image,
response_status,
parse_json,
open_image,
check_login
)
class Assistant(object):
def __init__(self):
self.user_agent = DEFAULT_USER_AGENT
self.headers = {
'User-Agent': self.user_agent,
'ContentType': 'text/html; charset=utf-8',
'Accept-Encoding': 'gzip, deflate, sdch',
'Accept-Language': 'zh-CN,zh;q=0.8',
'Connection': 'keep-alive',
}
self.nick_name = 'init_nick_name'
self.is_login = False
self.cookies_dict = {}
self.add_id = ''
self.qrToken = ''
self.sess = requests.Session()
try:
self._load_cookies()
except Exception:
logger.error('读取本地cookie报错')
pass
def marsCreat(self, leng=32):
t = math.ceil(time.time()*1000)
x = "0123456789abcdef"
tmp = ''
for i in range(leng):
r = math.ceil(random.random()*1E8) % len(x)
tmp += x[r:r+1]
return str(t) + "_" + tmp
def _load_cookies(self):
cookies_file = ''
for name in os.listdir('./cookies'):
if name.endswith('.cookies'):
cookies_file = './cookies/{0}'.format(name)
break
with open(cookies_file, 'rb') as f:
local_cookies = pickle.load(f)
self.sess.cookies.update(local_cookies)
res_cookies_dic = requests.utils.dict_from_cookiejar(local_cookies)
self.cookies_dict = res_cookies_dic
logger.info(self.cookies_dict)
self.is_login = self._validate_cookies()
def _save_cookies(self):
cookies_file = './cookies/{0}.cookies'.format(self.nick_name)
cookies_file_path = './new_cookies/cookies.text'
# res_cookies_dic = requests.utils.dict_from_cookiejar(self.sess.cookies)
directory = os.path.dirname(cookies_file)
if not os.path.exists(directory):
os.makedirs(directory)
with open(cookies_file, 'wb') as f:
pickle.dump(self.sess.cookies, f)
def _validate_cookies(self):
"""
验证cookies是否有效(是否登录)
通过访问用户订单列表页进行判断:若未登录,将会重定向到登录页面。
:return: cookies是否有效 True/False
"""
# url = 'https://order.vip.com/order/orderlist?orderStatus=all&type=all&pageNum=1'
# url = 'https://myi.vip.com/api/common/get_verify_token'
url = 'https://myi.vip.com/api/new_point/detail_list?cur_page=1&page_size=10&time_scope=THREE_MONTH&type=ALL'
headers = {
'User-Agent': self.user_agent,
'Referer': 'https://myi.vip.com/',
}
try:
resp = self.sess.get(url=url, headers=headers, allow_redirects=False)
if resp.status_code == requests.codes.OK:
logger.info('cookies验证有效')
return True
except Exception as e:
logger.error(e)
logger.info('验证cookies无效')
self.sess = requests.session()
return False
def _get_login_page(self):
"""
获取登录页面
url: https://passport.vip.com/login
:return:
"""
url = "https://passport.vip.com/login"
resp = self.sess.get(url, headers=self.headers)
# for k, v in resp.cookies.items():
# self.cookies[k] = v
self.sess.cookies['mars_cid'] = self.marsCreat()
self.headers['Host'] = 'passport.vip.com'
self.headers['Origin'] = 'https://passport.vip.com'
self.headers['Referer'] = 'https://passport.vip.com/login'
return resp
def _get_qrToken(self):
"""
获取qrToken参数
:return:
"""
url = "https://passport.vip.com/qrLogin/initQrLogin"
headers = {
'User-Agent': self.user_agent,
'Referer': 'https://passport.vip.com/login',
}
resp = self.sess.post(url, headers=headers)
resp_json = parse_json(resp.text)
if resp_json['code'] != 200:
logger.error('获取qrToken参数失败')
return False
else:
logger.info('获取qrToken参数成功')
self.qrToken = resp_json['qrToken']
return True
def _get_QRcode(self):
"""
获取二维码图片
:return:
"""
url = 'https://passport.vip.com/qrLogin/getQrImage'
payload = {
'qrToken': self.qrToken
}
headers = {
'User-Agent': self.user_agent,
'Referer': 'https://passport.vip.com/login',
}
resp = self.sess.get(url, headers=headers, params=payload)
if not response_status(resp):
logger.info('获取二维码失败')
return False
QRCode_file = 'QRcode.png'
save_image(resp, QRCode_file)
logger.info('二维码获取成功,请打开唯品会APP扫描')
open_image(QRCode_file)
return True
def _get_QRCode_ticket(self):
"""
检查是否扫描二维码
{"code":200,"msg":"执行成功","status":"INVALID","redirectUrl":null}
status: NOT_SCANNED--未扫描 SCANNED---已扫描 INVALID--无效 CONFIRMED--确认
:return:
"""
url = 'https://passport.vip.com/qrLogin/checkStatus'
payload = {
'qrToken': self.qrToken
}
headers = {
'User-Agent': self.user_agent,
'Referer': 'https://passport.vip.com/login',
}
resp = self.sess.post(url=url, headers=headers, params=payload)
if not response_status(resp):
raise AsstException('获取二维码扫描结果异常')
return False
resp_json = parse_json(resp.text)
if resp_json['code'] != 200:
logger.info('Code: %s, Message: %s, status: %s', resp_json['code'], resp_json['msg'], resp_json['status'])
return None
else:
logger.info('Code: %s, Message: %s, status: %s', resp_json['code'], resp_json['msg'], resp_json['status'])
if resp_json['status'] == 'NOT_SCANNED':
logger.info('未扫描')
return None
elif resp_json['status'] == 'INVALID':
logger.info('二维码过期,请重新获取扫描')
return None
elif resp_json['status'] == 'SCANNED':
logger.info('已扫描,未确认')
return None
else:
logger.info('已完成手机客户端确认')
return resp_json['status']
def login_by_QRcode(self):
"""
二维码登录
:return:
"""
if self.is_login:
logger.info('登录成功')
return
self._get_login_page()
self._get_qrToken()
if not self._get_QRcode():
logger.info('二维码下载失败')
for _ in range(30):
ticket = self._get_QRCode_ticket()
if ticket:
break
time.sleep(4)
else:
raise AsstException('二维码过期,请重新获取扫描')
logger.info('二维码登录成功')
self.is_login = True
self.nick_name = self.get_user_info()
self._save_cookies()
@check_login
def get_user_info(self):
"""
获取用户信息
:return:
"""
url = 'https://myi.vip.com/api/account/base_info'
headers = {
'User-Agent': self.user_agent,
'Referer': 'https://myi.vip.com/basicinfo.html',
}
try:
resp = self.sess.get(url=url, headers=headers)
resp_json = parse_json(resp.text)
logger.info(resp_json.get('data'))
return resp_json.get('data')['nickname'] or 'vip'
except Exception:
return 'vip'
def _get_item_detail_page(self, sku_code, sku_id):
"""
访问商品详情页
:param sku_code:
:param sku_id:
:return: 响应
"""
url = 'https://detail.vip.com/detail-{0}-{1}.html'.format(sku_code, sku_id)
headers = {
'User-Agent': self.user_agent,
}
logger.info('请求商品详情页:https://detail.vip.com/detail-{0}-{1}.html'.format(sku_code, sku_id))
page = requests.get(url=url, headers=headers)
cookies = {}
for k, v in page.cookies.items():
cookies[k] = v
logger.info(cookies)
# res_cookies_dic = requests.utils.dict_from_cookiejar(self.sess.cookies)
# logger.info(res_cookies_dic)
return page
@check_login
def if_item_can_be_ordered(self, sku_code, sku_id, areaId):
"""
判断商品是否能下单
:param sku_id:
:param areaId:
:return: True/False
"""
return self.get_single_item_stock(sku_code=sku_code, sku_id=sku_id, areaId=areaId)
def get_single_item_stock(self, sku_code, sku_id, areaId):
"""
获取单个商品库存状态
944103111102
:param sku_id:
:param areaId:
:return:
"""
page = self._get_item_detail_page(sku_code, sku_id)
url = 'https://stock.vip.com/detail'
payload = {
'callback': 'stock_detail',
'merchandiseId': sku_id,
'is_old': 1,
'areaId': areaId,
'_': round(time.time() * 1000),
}
headers = {
'User-Agent': self.user_agent,
'Referer': 'https://detail.vip.com/'
}
try:
resp = self.sess.get(url=url, headers=headers, params=payload)
resp_json = parse_json(resp.text)
item = resp_json.get('items')[0]
stock = item['stock']
self.add_id = item['id']
logger.info('当前库存: %s', stock)
return stock > 0
except requests.exceptions.Timeout:
logger.error('查询 %s 库存信息超时(%ss)', sku_id, self.timeout)
return False
except requests.exceptions.RequestException as request_exception:
logger.error('查询 %s 库存信息发生网络请求异常:%s', sku_id, request_exception)
return False
except Exception as e:
logger.error('查询 %s 库存信息发生异常, resp: %s, exception: %s', sku_id, e)
return False
def add_item_to_cart(self, sku_id, areaId):
"""
添加商品到购物车
:param sku_id:
:return:
"""
url = 'https://mapi.vip.com/vips-mobile/rest/cart/pc/add_cart'
headers = {
'User-Agent': self.user_agent,
'Referer': url
}
payload = {
'callback': 'addCart',
'app_name': 'shop_pc',
'app_version': '4.0',
'warehouse': 'VIP_NH',
'fdc_area_id': areaId,
'client': 'pc',
'mobile_platform': '1',
'province_id': '104104',
'api_key': '70f71280d5d547b2a7bb370a529aeea1',
'user_id': '290090873',
'mars_cid': self.cookies_dict['mars_cid'],
'wap_consumer': 'c',
'size_id': self.add_id,
'size_num': 1,
'product_id': sku_id,
'source_app': 'pc',
'captcha_id': '',
'ticket': '',
'is_reserve': '0',
'cart_ver': '4',
'functions': 'canChecked',
'_': round(time.time() * 1000),
}
logger.info('下单请求:https://mapi.vip.com/vips-mobile/rest/cart/pc/add_cart')
resp = self.sess.get(url=url, headers=headers, params=payload)
resp_json = parse_json(resp.text)
logger.info(resp_json)
def _add_or_change_cart_item(self, sku_id, count=1):
"""
添加商品到购物车
:param sku_id:
:param count:
:return:
"""
logger.info('开始加入购物车,数量 %s', count)
def buy_item_in_stock(self, sku_code, sku_id, areaId, wait_all=False, stock_interval=3, submit_retry=3, submit_interval=5):
"""
:param sku_code:
:param sku_id:
:param areaId:
:param wait_all: 是否等所有商品都有货才一起下单,可选参数,默认False
:param stock_interval: 查询库存时间间隔,可选参数,默认3秒
:param submit_retry: 提交订单失败后重试次数,可选参数,默认3次
:param submit_interval: 提交订单失败后重试时间间隔,可选参数,默认5秒
:return:
"""
if not wait_all:
logger.info('下单模式:%s 任一商品有货并且未下架均会尝试下单', sku_id)
while True:
if not self.if_item_can_be_ordered(sku_code=sku_code, sku_id=sku_id, areaId=areaId):
logger.info('%s 不满足下单条件,%ss后进行下一次查询', sku_id, stock_interval)
else:
logger.info('%s 满足下单条件,开始执行', sku_id)
self.add_item_to_cart(sku_id=sku_id, areaId=areaId)
time.sleep(stock_interval)
def arr(self):
"""
https://mapi.vip.com/vips-mobile/rest/cart/pc/get_shopping_cart/v2 购物车获取订单
:return:
"""
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。