|
|
|
import math
|
|
|
|
import functools
|
|
|
|
from lxml import html
|
|
|
|
import requests
|
|
|
|
import time
|
|
|
|
import json
|
|
|
|
import random
|
|
|
|
import sys
|
|
|
|
from concurrent.futures import ProcessPoolExecutor
|
|
|
|
from exception import SKException
|
|
|
|
from bs4 import BeautifulSoup
|
|
|
|
from config import global_config
|
|
|
|
from logger import logger
|
|
|
|
from login import SpiderSession, QrLogin
|
|
|
|
from message import sendMessage
|
|
|
|
from timer import Timer
|
|
|
|
from util import parse_json
|
|
|
|
|
|
|
|
|
|
|
|
class Waiter():
|
|
|
|
def __init__(self):
|
|
|
|
self.spider_session = SpiderSession()
|
|
|
|
self.spider_session.load_cookies_from_local()
|
|
|
|
self.session = self.spider_session.get_session()
|
|
|
|
self.qrlogin = QrLogin(self.spider_session)
|
|
|
|
self.skuids = global_config.getRaw("config", "sku_id")
|
|
|
|
self.area = global_config.getRaw("config", "area")
|
|
|
|
self.eid = global_config.getRaw("config", "eid")
|
|
|
|
self.fp = global_config.getRaw("config", "fp")
|
|
|
|
self.user_agent = self.spider_session.user_agent
|
|
|
|
self.item_cat = dict()
|
|
|
|
self.item_vender_ids = dict() # 记录商家id
|
|
|
|
self.timeout = float(global_config.getRaw('config', 'timeout'))
|
|
|
|
self.headers = {'User-Agent': self.user_agent}
|
|
|
|
self.timers = Timer()
|
|
|
|
|
|
|
|
def login_by_qrcode(self):
|
|
|
|
"""
|
|
|
|
二维码登陆
|
|
|
|
:return:
|
|
|
|
"""
|
|
|
|
if self.qrlogin.is_login:
|
|
|
|
logger.info('登录成功')
|
|
|
|
return
|
|
|
|
|
|
|
|
self.qrlogin.login_by_qrcode()
|
|
|
|
|
|
|
|
if self.qrlogin.is_login:
|
|
|
|
self.nick_name = self.getUsername()
|
|
|
|
self.spider_session.save_cookies_to_local(self.nick_name)
|
|
|
|
else:
|
|
|
|
raise SKException("二维码登录失败!")
|
|
|
|
|
|
|
|
def check_login(func):
|
|
|
|
"""
|
|
|
|
用户登陆态校验装饰器。若用户未登陆,则调用扫码登陆
|
|
|
|
"""
|
|
|
|
@functools.wraps(func)
|
|
|
|
def new_func(self, *args, **kwargs):
|
|
|
|
if not self.qrlogin.is_login:
|
|
|
|
logger.info("{0} 需登陆后调用,开始扫码登陆".format(func.__name__))
|
|
|
|
self.login_by_qrcode()
|
|
|
|
return func(self, *args, **kwargs)
|
|
|
|
return new_func
|
|
|
|
|
|
|
|
def waitForSell(self):
|
|
|
|
self._waitForSell()
|
|
|
|
|
|
|
|
def waitTimeForSell(self):
|
|
|
|
self._waitTimeForSell()
|
|
|
|
|
|
|
|
def get_tag_value(self, tag, key='', index=0):
|
|
|
|
if key:
|
|
|
|
value = tag[index].get(key)
|
|
|
|
else:
|
|
|
|
value = tag[index].text
|
|
|
|
return value.strip(' \t\r\n')
|
|
|
|
|
|
|
|
def response_status(self, resp):
|
|
|
|
if resp.status_code != requests.codes.OK:
|
|
|
|
print('Status: %u, Url: %s' % (resp.status_code, resp.url))
|
|
|
|
return False
|
|
|
|
return True
|
|
|
|
|
|
|
|
def getUsername(self):
|
|
|
|
userName_Url = 'https://passport.jd.com/new/helloService.ashx?callback=jQuery339448&_=' + str(
|
|
|
|
int(time.time() * 1000))
|
|
|
|
self.session.headers = {
|
|
|
|
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/531.36",
|
|
|
|
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3",
|
|
|
|
"Referer": "https://order.jd.com/center/list.action",
|
|
|
|
"Connection": "keep-alive"
|
|
|
|
}
|
|
|
|
resp = self.session.get(url=userName_Url, allow_redirects=True)
|
|
|
|
resultText = resp.text
|
|
|
|
resultText = resultText.replace('jQuery339448(', '')
|
|
|
|
resultText = resultText.replace(')', '')
|
|
|
|
usernameJson = json.loads(resultText)
|
|
|
|
logger.info('登录账号名称' + usernameJson['nick'])
|
|
|
|
return usernameJson['nick']
|
|
|
|
|
|
|
|
def get_sku_title(self):
|
|
|
|
"""获取商品名称"""
|
|
|
|
url = 'https://item.jd.com/{}.html'.format(global_config.getRaw('config', 'sku_id'))
|
|
|
|
resp = self.session.get(url).content
|
|
|
|
x_data = html.etree.HTML(resp)
|
|
|
|
sku_title = x_data.xpath('/html/head/title/text()')
|
|
|
|
return sku_title[0]
|
|
|
|
|
|
|
|
@check_login
|
|
|
|
def waitAndBuy_by_proc_pool(self):
|
|
|
|
"""
|
|
|
|
多进程进行抢购
|
|
|
|
work_count:进程数量
|
|
|
|
"""
|
|
|
|
work_count = eval(global_config.getRaw('settings', 'work_count'))
|
|
|
|
with ProcessPoolExecutor(work_count) as pool:
|
|
|
|
for i in range(work_count):
|
|
|
|
pool.submit(self.buy)
|
|
|
|
|
|
|
|
|
|
|
|
'''
|
|
|
|
检查是否有货
|
|
|
|
'''
|
|
|
|
|
|
|
|
|
|
|
|
def check_item_stock(self):
|
|
|
|
stockurl = 'http://c0.3.cn/stock?skuId=' + self.skuids + '&cat=652,829,854&area=' + self.area + '&extraParam={%22originid%22:%221%22}'
|
|
|
|
response = self.session.get(stockurl)
|
|
|
|
resp = self.session.get(stockurl)
|
|
|
|
jsparser = json.loads(resp.text)
|
|
|
|
# 33 有货 34 无货
|
|
|
|
if jsparser['StockState'] == 33 and jsparser['StockStateName'] == '现货':
|
|
|
|
print('库存状态:', jsparser['StockStateName'])
|
|
|
|
return True
|
|
|
|
else:
|
|
|
|
print('库存状态:{}(无现货)'.format(jsparser['StockStateName']))
|
|
|
|
return False
|
|
|
|
|
|
|
|
def _get_item_detail_page(self, sku_id):
|
|
|
|
"""访问商品详情页
|
|
|
|
:param sku_id: 商品id
|
|
|
|
:return: 响应
|
|
|
|
"""
|
|
|
|
url = 'https://item.jd.com/{}.html'.format(sku_id)
|
|
|
|
page = requests.get(url=url, headers=self.headers)
|
|
|
|
return page
|
|
|
|
|
|
|
|
def _waitForSell(self):
|
|
|
|
area_id = self.area
|
|
|
|
sku_id = self.skuids
|
|
|
|
logger.info("正在等待商品上架:{}".format(self.get_sku_title()[:80] + " ......"))
|
|
|
|
while True:
|
|
|
|
if self.get_single_item_stock(sku_id, area_id):
|
|
|
|
sendMessage("商品上架: {}".format(self.get_sku_title()[:80] + " ......"))
|
|
|
|
logger.info("商品上架: {}".format(self.get_sku_title()[:80] + " ......"))
|
|
|
|
# self.waitAndBuy_by_proc_pool()
|
|
|
|
self.buy()
|
|
|
|
else:
|
|
|
|
time.sleep(random.randint(1, 10))
|
|
|
|
|
|
|
|
def _waitTimeForSell(self):
|
|
|
|
self.initCart()
|
|
|
|
logger.info("正在等待商品上架:{}".format(self.get_sku_title()[:80] + " ......"))
|
|
|
|
self.timers.start()
|
|
|
|
self.fastBuy()
|
|
|
|
|
|
|
|
|
|
|
|
def get_single_item_stock(self, sku_id, area_id):
|
|
|
|
"""获取单个商品库存状态
|
|
|
|
:param sku_id: 商品id
|
|
|
|
:param num: 商品数量
|
|
|
|
:param area: 地区id
|
|
|
|
:return: 商品是否有货 True/False
|
|
|
|
"""
|
|
|
|
url = 'https://cd.jd.com/stocks'
|
|
|
|
# https://cd.jd.com/stocks?callback=jQuery3528455&type=getstocks&skuIds=100011513445&area=21_1827_4101_40925&_=1625970219360
|
|
|
|
payload = {
|
|
|
|
'callback': 'jQuery{}'.format(random.randint(1000000, 9999999)),
|
|
|
|
'type': 'getstocks',
|
|
|
|
'skuIds': sku_id,
|
|
|
|
'area': area_id,
|
|
|
|
'_': str(int(time.time() * 1000)),
|
|
|
|
}
|
|
|
|
headers = {
|
|
|
|
'User-Agent': self.user_agent,
|
|
|
|
'Referer': 'https://item.jd.com/{}.html'.format(sku_id),
|
|
|
|
}
|
|
|
|
|
|
|
|
resp_text = ''
|
|
|
|
try:
|
|
|
|
resp_text = requests.get(url=url, params=payload, headers=headers, timeout=self.timeout).text
|
|
|
|
resp_json = parse_json(resp_text)
|
|
|
|
stock_info = resp_json.get(sku_id)
|
|
|
|
sku_state = stock_info.get('skuState') # 商品是否上架
|
|
|
|
stock_state = stock_info.get('StockState') # 商品库存状态:33 -- 现货 0,34 -- 无货 36 -- 采购中 40 -- 可配货
|
|
|
|
if sku_state == 1 and stock_state in (33, 40):
|
|
|
|
logger.info("有货: " + stock_info.get('StockStateName'))
|
|
|
|
return True
|
|
|
|
# else:
|
|
|
|
# logger.info(stock_info.get('StockStateName'))
|
|
|
|
# return False
|
|
|
|
except requests.exceptions.Timeout:
|
|
|
|
logger.error('查询 %s 库存信息超时(%ss)', sku_id, self.timeout)
|
|
|
|
return False
|
|
|
|
except requests.exceptions.RequestException as request_exception:
|
|
|
|
logger.error('查询 %s 库存信息发生网络请求异常:\n%s', sku_id, request_exception)
|
|
|
|
return False
|
|
|
|
except Exception as e:
|
|
|
|
logger.error('查询 %s 库存信息发生异常:\nresp: %s\nexception: %s', sku_id, resp_text, e)
|
|
|
|
return False
|
|
|
|
|
|
|
|
# from selenium import webdriver
|
|
|
|
# from selenium.webdriver.chrome.options import Options
|
|
|
|
# def getInfo_selenium(self):
|
|
|
|
# """
|
|
|
|
# 等待进行抢购
|
|
|
|
# """
|
|
|
|
# config_headless = True
|
|
|
|
#
|
|
|
|
# chrome_options = Options()
|
|
|
|
# chrome_options.add_argument(self.user_agent)
|
|
|
|
# chrome_options.add_argument("--profile-directory=Default")
|
|
|
|
# chrome_options.add_argument("--whitelisted-ips")
|
|
|
|
# chrome_options.add_argument("--start-maximized")
|
|
|
|
# chrome_options.add_argument("--disable-extensions")
|
|
|
|
# chrome_options.add_argument("--disable-plugins-discovery")
|
|
|
|
#
|
|
|
|
# if config_headless == True:
|
|
|
|
# # 此处开启后为无头浏览器
|
|
|
|
# chrome_options.add_argument('--headless')
|
|
|
|
# chrome_options.add_argument('--no-sandbox')
|
|
|
|
# chrome_options.add_argument('--disable-dev-shm-usage')
|
|
|
|
# chrome_options.add_argument('blink-settings=imagesEnabled=false')
|
|
|
|
# chrome_options.add_argument('--disable-gpu')
|
|
|
|
#
|
|
|
|
# logger.info("正在等待商品上架:{}".format(self.get_sku_title()[:80] + " ......"))
|
|
|
|
# driver = webdriver.Chrome('/usr/local/bin/chromedriver', options=chrome_options)
|
|
|
|
# driver.maximize_window()
|
|
|
|
#
|
|
|
|
# while True:
|
|
|
|
# state = False
|
|
|
|
# url = 'https://item.jd.com/{}.html'.format(global_config.getRaw('config', 'sku_id'))
|
|
|
|
# driver.get(url)
|
|
|
|
# time.sleep(60)
|
|
|
|
# buttonValue = driver.find_element_by_xpath(
|
|
|
|
# "/html/body/div[@class='w']/div[@class='product-intro clearfix']/div[@class='itemInfo-wrap']/div[@class='summary p-choose-wrap']/div[@id='choose-btns']/a[@id='InitCartUrl']").get_attribute(
|
|
|
|
# "class")
|
|
|
|
# # joinCart = x_data.xpath("/html/body/div[@class='w']/div[@class='product-intro clearfix']/div[@class='itemInfo-wrap']/div[@class='summary p-choose-wrap']/div[@id='choose-btns']/a[@id='InitCartUrl']/@class")
|
|
|
|
# # notice = x_data.xpath("/html/body/div[@class='w']/div[@class='product-intro clearfix']/div[@class='itemInfo-wrap']/div[@class='summary p-choose-wrap']/div[@id='choose-btns']/a[@id='btn-notify']/text()")
|
|
|
|
# if buttonValue == "btn-special1 btn-lg btn-disable": # 没货
|
|
|
|
# State = False
|
|
|
|
# else:
|
|
|
|
# state = True
|
|
|
|
# if state:
|
|
|
|
# driver.quit()
|
|
|
|
# sendMessage("商品上架: {}".format(self.get_sku_title()[:80] + " ......"))
|
|
|
|
# logger.info("商品上架: {}".format(self.get_sku_title()[:80] + " ......"))
|
|
|
|
# # self.waitAndBuy_by_proc_pool()
|
|
|
|
# self.buy()
|
|
|
|
# else:
|
|
|
|
# pass
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
'''
|
|
|
|
取消勾选购物车中的所有商品
|
|
|
|
'''
|
|
|
|
|
|
|
|
|
|
|
|
def cancel_select_all_cart_item(self):
|
|
|
|
url = "https://cart.jd.com/cancelAllItem.action"
|
|
|
|
data = {
|
|
|
|
't': 0,
|
|
|
|
'outSkus': '',
|
|
|
|
'random': random.random()
|
|
|
|
}
|
|
|
|
resp = self.session.post(url, data=data)
|
|
|
|
if resp.status_code != requests.codes.OK:
|
|
|
|
print('Status: %u, Url: %s' % (resp.status_code, resp.url))
|
|
|
|
return False
|
|
|
|
return True
|
|
|
|
|
|
|
|
'''
|
|
|
|
勾选购物车中的所有商品
|
|
|
|
'''
|
|
|
|
|
|
|
|
|
|
|
|
def select_all_cart_item(self):
|
|
|
|
url = "https://cart.jd.com/selectAllItem.action"
|
|
|
|
data = {
|
|
|
|
't': 0,
|
|
|
|
'outSkus': '',
|
|
|
|
'random': random.random()
|
|
|
|
}
|
|
|
|
resp = self.session.post(url, data=data)
|
|
|
|
if resp.status_code != requests.codes.OK:
|
|
|
|
print('Status: %u, Url: %s' % (resp.status_code, resp.url))
|
|
|
|
return False
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
'''
|
|
|
|
删除购物车选中商品
|
|
|
|
'''
|
|
|
|
|
|
|
|
|
|
|
|
def remove_item(self):
|
|
|
|
url = "https://cart.jd.com/batchRemoveSkusFromCart.action"
|
|
|
|
data = {
|
|
|
|
't': 0,
|
|
|
|
'null': '',
|
|
|
|
'outSkus': '',
|
|
|
|
'random': random.random(),
|
|
|
|
'locationId': '19-1607-4773-0'
|
|
|
|
}
|
|
|
|
headers = {
|
|
|
|
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.25 Safari/537.36 Core/1.70.37",
|
|
|
|
"Accept": "application/json, text/javascript, */*; q=0.01",
|
|
|
|
"Referer": "https://cart.jd.com/cart.action",
|
|
|
|
"Host": "cart.jd.com",
|
|
|
|
"Content-Type": "application/x-www-form-urlencoded",
|
|
|
|
"Accept-Encoding": "gzip, deflate, br",
|
|
|
|
"Accept-Encoding": "zh-CN,zh;q=0.9,ja;q=0.8",
|
|
|
|
"Origin": "https://cart.jd.com",
|
|
|
|
"Connection": "keep-alive"
|
|
|
|
}
|
|
|
|
resp = self.session.post(url, data=data, headers=headers)
|
|
|
|
logger.info('清空购物车')
|
|
|
|
if resp.status_code != requests.codes.OK:
|
|
|
|
print('Status: %u, Url: %s' % (resp.status_code, resp.url))
|
|
|
|
return False
|
|
|
|
return True
|
|
|
|
|
|
|
|
'''
|
|
|
|
购物车详情
|
|
|
|
'''
|
|
|
|
|
|
|
|
|
|
|
|
def cart_detail(self):
|
|
|
|
url = 'https://cart.jd.com/cart.action'
|
|
|
|
headers = {
|
|
|
|
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/531.36",
|
|
|
|
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3",
|
|
|
|
"Referer": "https://order.jd.com/center/list.action",
|
|
|
|
"Host": "cart.jd.com",
|
|
|
|
"Connection": "keep-alive"
|
|
|
|
}
|
|
|
|
resp = self.session.get(url, headers=headers)
|
|
|
|
soup = BeautifulSoup(resp.text, "html.parser")
|
|
|
|
|
|
|
|
cart_detail = dict()
|
|
|
|
for item in soup.find_all(class_='item-item'):
|
|
|
|
try:
|
|
|
|
sku_id = item['skuid'] # 商品id
|
|
|
|
except Exception as e:
|
|
|
|
logger.info('购物车中有套装商品,跳过')
|
|
|
|
continue
|
|
|
|
try:
|
|
|
|
# 例如:['increment', '8888', '100001071956', '1', '13', '0', '50067652554']
|
|
|
|
# ['increment', '8888', '100002404322', '2', '1', '0']
|
|
|
|
item_attr_list = item.find(class_='increment')['id'].split('_')
|
|
|
|
p_type = item_attr_list[4]
|
|
|
|
promo_id = target_id = item_attr_list[-1] if len(item_attr_list) == 7 else 0
|
|
|
|
|
|
|
|
cart_detail[sku_id] = {
|
|
|
|
'name': self.get_tag_value(item.select('div.p-name a')), # 商品名称
|
|
|
|
'verder_id': item['venderid'], # 商家id
|
|
|
|
'count': int(item['num']), # 数量
|
|
|
|
'unit_price': self.get_tag_value(item.select('div.p-price strong'))[1:], # 单价
|
|
|
|
'total_price': self.get_tag_value(item.select('div.p-sum strong'))[1:], # 总价
|
|
|
|
'is_selected': 'item-selected' in item['class'], # 商品是否被勾选
|
|
|
|
'p_type': p_type,
|
|
|
|
'target_id': target_id,
|
|
|
|
'promo_id': promo_id
|
|
|
|
}
|
|
|
|
except Exception as e:
|
|
|
|
logger.error("商品%s在购物车中的信息无法解析,报错信息: %s,该商品自动忽略", sku_id, e)
|
|
|
|
|
|
|
|
logger.info('购物车信息:%s', cart_detail)
|
|
|
|
return cart_detail
|
|
|
|
|
|
|
|
|
|
|
|
'''
|
|
|
|
修改购物车商品的数量
|
|
|
|
'''
|
|
|
|
|
|
|
|
|
|
|
|
def change_item_num_in_cart(self, sku_id, vender_id, num, p_type, target_id, promo_id):
|
|
|
|
url = "https://cart.jd.com/changeNum.action"
|
|
|
|
data = {
|
|
|
|
't': 0,
|
|
|
|
'venderId': vender_id,
|
|
|
|
'pid': sku_id,
|
|
|
|
'pcount': num,
|
|
|
|
'ptype': p_type,
|
|
|
|
'targetId': target_id,
|
|
|
|
'promoID': promo_id,
|
|
|
|
'outSkus': '',
|
|
|
|
'random': random.random(),
|
|
|
|
# 'locationId'
|
|
|
|
}
|
|
|
|
self.session.headers = {
|
|
|
|
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/531.36",
|
|
|
|
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3",
|
|
|
|
"Referer": "https://cart.jd.com/cart",
|
|
|
|
"Connection": "keep-alive"
|
|
|
|
}
|
|
|
|
resp = self.session.post(url, data=data)
|
|
|
|
return json.loads(resp.text)['sortedWebCartResult']['achieveSevenState'] == 2
|
|
|
|
|
|
|
|
|
|
|
|
'''
|
|
|
|
添加商品到购物车
|
|
|
|
'''
|
|
|
|
|
|
|
|
|
|
|
|
def add_item_to_cart(self, sku_id):
|
|
|
|
url = 'https://cart.jd.com/gate.action'
|
|
|
|
payload = {
|
|
|
|
'pid': sku_id,
|
|
|
|
'pcount': 1,
|
|
|
|
'ptype': 1,
|
|
|
|
}
|
|
|
|
resp = self.session.get(url=url, params=payload)
|
|
|
|
if 'https://cart.jd.com/cart.action' in resp.url: # 套装商品加入购物车后直接跳转到购物车页面
|
|
|
|
result = True
|
|
|
|
else: # 普通商品成功加入购物车后会跳转到提示 "商品已成功加入购物车!" 页面
|
|
|
|
soup = BeautifulSoup(resp.text, "html.parser")
|
|
|
|
result = bool(soup.select('h3.ftx-02')) # [<h3 class="ftx-02">商品已成功加入购物车!</h3>]
|
|
|
|
|
|
|
|
if result:
|
|
|
|
logger.info('%s 已成功加入购物车', sku_id)
|
|
|
|
else:
|
|
|
|
logger.error('%s 添加到购物车失败', sku_id)
|
|
|
|
|
|
|
|
|
|
|
|
def get_checkout_page_detail(self):
|
|
|
|
"""获取订单结算页面信息
|
|
|
|
|
|
|
|
该方法会返回订单结算页面的详细信息:商品名称、价格、数量、库存状态等。
|
|
|
|
|
|
|
|
:return: 结算信息 dict
|
|
|
|
"""
|
|
|
|
url = 'http://trade.jd.com/shopping/order/getOrderInfo.action'
|
|
|
|
# url = 'https://cart.jd.com/gotoOrder.action'
|
|
|
|
payload = {
|
|
|
|
'rid': str(int(time.time() * 1000)),
|
|
|
|
}
|
|
|
|
headers = {
|
|
|
|
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/531.36",
|
|
|
|
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3",
|
|
|
|
"Referer": "https://cart.jd.com/cart.action",
|
|
|
|
"Connection": "keep-alive",
|
|
|
|
'Host': 'trade.jd.com',
|
|
|
|
}
|
|
|
|
try:
|
|
|
|
resp = self.session.get(url=url, params=payload, headers=headers)
|
|
|
|
if not self.response_status(resp):
|
|
|
|
logger.error('获取订单结算页信息失败')
|
|
|
|
return ''
|
|
|
|
|
|
|
|
soup = BeautifulSoup(resp.text, "html.parser")
|
|
|
|
risk_control = self.get_tag_value(soup.select('input#riskControl'), 'value')
|
|
|
|
|
|
|
|
order_detail = {
|
|
|
|
'address': soup.find('span', id='sendAddr').text[5:], # remove '寄送至: ' from the begin
|
|
|
|
'receiver': soup.find('span', id='sendMobile').text[4:], # remove '收件人:' from the begin
|
|
|
|
'total_price': soup.find('span', id='sumPayPriceId').text[1:], # remove '¥' from the begin
|
|
|
|
'items': []
|
|
|
|
}
|
|
|
|
|
|
|
|
logger.info("下单信息:%s", order_detail)
|
|
|
|
return order_detail
|
|
|
|
except requests.exceptions.RequestException as e:
|
|
|
|
logger.error('订单结算页面获取异常:%s' % e)
|
|
|
|
except Exception as e:
|
|
|
|
logger.error('下单页面数据解析异常:%s', e)
|
|
|
|
return -1
|
|
|
|
|
|
|
|
|
|
|
|
def submit_order(self, risk_control):
|
|
|
|
"""提交订单
|
|
|
|
|
|
|
|
重要:
|
|
|
|
1.该方法只适用于普通商品的提交订单(即可以加入购物车,然后结算提交订单的商品)
|
|
|
|
2.提交订单时,会对购物车中勾选✓的商品进行结算(如果勾选了多个商品,将会提交成一个订单)
|
|
|
|
|
|
|
|
:return: True/False 订单提交结果
|
|
|
|
"""
|
|
|
|
url = 'https://trade.jd.com/shopping/order/submitOrder.action'
|
|
|
|
# js function of submit order is included in https://trade.jd.com/shopping/misc/js/order.js?r=2018070403091
|
|
|
|
|
|
|
|
# overseaPurchaseCookies:
|
|
|
|
# vendorRemarks: []
|
|
|
|
# submitOrderParam.sopNotPutInvoice: false
|
|
|
|
# submitOrderParam.trackID: TestTrackId
|
|
|
|
# submitOrderParam.ignorePriceChange: 0
|
|
|
|
# submitOrderParam.btSupport: 0
|
|
|
|
# riskControl:
|
|
|
|
# submitOrderParam.isBestCoupon: 1
|
|
|
|
# submitOrderParam.jxj: 1
|
|
|
|
# submitOrderParam.trackId:
|
|
|
|
|
|
|
|
data = {
|
|
|
|
'overseaPurchaseCookies': '',
|
|
|
|
'vendorRemarks': '[]',
|
|
|
|
'submitOrderParam.sopNotPutInvoice': 'false',
|
|
|
|
'submitOrderParam.trackID': 'TestTrackId',
|
|
|
|
'submitOrderParam.ignorePriceChange': '0',
|
|
|
|
'submitOrderParam.btSupport': '0',
|
|
|
|
'riskControl': risk_control,
|
|
|
|
'submitOrderParam.isBestCoupon': 1,
|
|
|
|
'submitOrderParam.jxj': 1,
|
|
|
|
'submitOrderParam.trackId': '9643cbd55bbbe103eef18a213e069eb0', # Todo: need to get trackId
|
|
|
|
# 'submitOrderParam.eid': eid,
|
|
|
|
# 'submitOrderParam.fp': fp,
|
|
|
|
'submitOrderParam.needCheck': 1,
|
|
|
|
}
|
|
|
|
|
|
|
|
def encrypt_payment_pwd(payment_pwd):
|
|
|
|
return ''.join(['u3' + x for x in payment_pwd])
|
|
|
|
self.payment_pwd=global_config.getRaw("account", "payment_pwd")
|
|
|
|
if len(self.payment_pwd) > 0:
|
|
|
|
data['submitOrderParam.payPassword'] = encrypt_payment_pwd(self.payment_pwd)
|
|
|
|
|
|
|
|
headers = {
|
|
|
|
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/531.36",
|
|
|
|
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3",
|
|
|
|
"Referer": "http://trade.jd.com/shopping/order/getOrderInfo.action",
|
|
|
|
"Connection": "keep-alive",
|
|
|
|
'Host': 'trade.jd.com',
|
|
|
|
}
|
|
|
|
|
|
|
|
try:
|
|
|
|
resp = self.session.post(url=url, data=data, headers=headers)
|
|
|
|
resp_json = json.loads(resp.text)
|
|
|
|
|
|
|
|
# 返回信息示例:
|
|
|
|
# 下单失败
|
|
|
|
# {'overSea': False, 'orderXml': None, 'cartXml': None, 'noStockSkuIds': '', 'reqInfo': None, 'hasJxj': False, 'addedServiceList': None, 'sign': None, 'pin': 'xxx', 'needCheckCode': False, 'success': False, 'resultCode': 60123, 'orderId': 0, 'submitSkuNum': 0, 'deductMoneyFlag': 0, 'goJumpOrderCenter': False, 'payInfo': None, 'scaleSkuInfoListVO': None, 'purchaseSkuInfoListVO': None, 'noSupportHomeServiceSkuList': None, 'msgMobile': None, 'addressVO': None, 'msgUuid': None, 'message': '请输入支付密码!'}
|
|
|
|
# {'overSea': False, 'cartXml': None, 'noStockSkuIds': '', 'reqInfo': None, 'hasJxj': False, 'addedServiceList': None, 'orderXml': None, 'sign': None, 'pin': 'xxx', 'needCheckCode': False, 'success': False, 'resultCode': 60017, 'orderId': 0, 'submitSkuNum': 0, 'deductMoneyFlag': 0, 'goJumpOrderCenter': False, 'payInfo': None, 'scaleSkuInfoListVO': None, 'purchaseSkuInfoListVO': None, 'noSupportHomeServiceSkuList': None, 'msgMobile': None, 'addressVO': None, 'msgUuid': None, 'message': '您多次提交过快,请稍后再试'}
|
|
|
|
# {'overSea': False, 'orderXml': None, 'cartXml': None, 'noStockSkuIds': '', 'reqInfo': None, 'hasJxj': False, 'addedServiceList': None, 'sign': None, 'pin': 'xxx', 'needCheckCode': False, 'success': False, 'resultCode': 60077, 'orderId': 0, 'submitSkuNum': 0, 'deductMoneyFlag': 0, 'goJumpOrderCenter': False, 'payInfo': None, 'scaleSkuInfoListVO': None, 'purchaseSkuInfoListVO': None, 'noSupportHomeServiceSkuList': None, 'msgMobile': None, 'addressVO': None, 'msgUuid': None, 'message': '获取用户订单信息失败'}
|
|
|
|
# {"cartXml":null,"noStockSkuIds":"xxx","reqInfo":null,"hasJxj":false,"addedServiceList":null,"overSea":false,"orderXml":null,"sign":null,"pin":"xxx","needCheckCode":false,"success":false,"resultCode":600157,"orderId":0,"submitSkuNum":0,"deductMoneyFlag":0,"goJumpOrderCenter":false,"payInfo":null,"scaleSkuInfoListVO":null,"purchaseSkuInfoListVO":null,"noSupportHomeServiceSkuList":null,"msgMobile":null,"addressVO":{"pin":"xxx","areaName":"","provinceId":xx,"cityId":xx,"countyId":xx,"townId":xx,"paymentId":0,"selected":false,"addressDetail":"xx","mobile":"xx","idCard":"","phone":null,"email":null,"selfPickMobile":null,"selfPickPhone":null,"provinceName":null,"cityName":null,"countyName":null,"townName":null,"giftSenderConsigneeName":null,"giftSenderConsigneeMobile":null,"gcLat":0.0,"gcLng":0.0,"coord_type":0,"longitude":0.0,"latitude":0.0,"selfPickOptimize":0,"consigneeId":0,"selectedAddressType":0,"siteType":0,"helpMessage":null,"tipInfo":null,"cabinetAvailable":true,"limitKeyword":0,"specialRemark":null,"siteProvinceId":0,"siteCityId":0,"siteCountyId":0,"siteTownId":0,"skuSupported":false,"addressSupported":0,"isCod":0,"consigneeName":null,"pickVOname":null,"shipmentType":0,"retTag":0,"tagSource":0,"userDefinedTag":null,"newProvinceId":0,"newCityId":0,"newCountyId":0,"newTownId":0,"newProvinceName":null,"newCityName":null,"newCountyName":null,"newTownName":null,"checkLevel":0,"optimizePickID":0,"pickType":0,"dataSign":0,"overseas":0,"areaCode":null,"nameCode":null,"appSelfPickAddress":0,"associatePickId":0,"associateAddressId":0,"appId":null,"encryptText":null,"certNum":null,"used":false,"oldAddress":false,"mapping":false,"addressType":0,"fullAddress":"xxxx","postCode":null,"addressDefault":false,"addressName":null,"selfPickAddressShuntFlag":0,"pickId":0,"pickName":null,"pickVOselected":false,"mapUrl":null,"branchId":0,"canSelected":false,"address":null,"name":"xxx","message":null,"id":0},"msgUuid":null,"message":"xxxxxx商品无货"}
|
|
|
|
# {'orderXml': None, 'overSea': False, 'noStockSkuIds': 'xxx', 'reqInfo': None, 'hasJxj': False, 'addedServiceList': None, 'cartXml': None, 'sign': None, 'pin': 'xxx', 'needCheckCode': False, 'success': False, 'resultCode': 600158, 'orderId': 0, 'submitSkuNum': 0, 'deductMoneyFlag': 0, 'goJumpOrderCenter': False, 'payInfo': None, 'scaleSkuInfoListVO': None, 'purchaseSkuInfoListVO': None, 'noSupportHomeServiceSkuList': None, 'msgMobile': None, 'addressVO': {'oldAddress': False, 'mapping': False, 'pin': 'xxx', 'areaName': '', 'provinceId': xx, 'cityId': xx, 'countyId': xx, 'townId': xx, 'paymentId': 0, 'selected': False, 'addressDetail': 'xxxx', 'mobile': 'xxxx', 'idCard': '', 'phone': None, 'email': None, 'selfPickMobile': None, 'selfPickPhone': None, 'provinceName': None, 'cityName': None, 'countyName': None, 'townName': None, 'giftSenderConsigneeName': None, 'giftSenderConsigneeMobile': None, 'gcLat': 0.0, 'gcLng': 0.0, 'coord_type': 0, 'longitude': 0.0, 'latitude': 0.0, 'selfPickOptimize': 0, 'consigneeId': 0, 'selectedAddressType': 0, 'newCityName': None, 'newCountyName': None, 'newTownName': None, 'checkLevel': 0, 'optimizePickID': 0, 'pickType': 0, 'dataSign': 0, 'overseas': 0, 'areaCode': None, 'nameCode': None, 'appSelfPickAddress': 0, 'associatePickId': 0, 'associateAddressId': 0, 'appId': None, 'encryptText': None, 'certNum': None, 'addressType': 0, 'fullAddress': 'xxxx', 'postCode': None, 'addressDefault': False, 'addressName': None, 'selfPickAddressShuntFlag': 0, 'pickId': 0, 'pickName': None, 'pickVOselected': False, 'mapUrl': None, 'branchId': 0, 'canSelected': False, 'siteType': 0, 'helpMessage': None, 'tipInfo': None, 'cabinetAvailable': True, 'limitKeyword': 0, 'specialRemark': None, 'siteProvinceId': 0, 'siteCityId': 0, 'siteCountyId': 0, 'siteTownId': 0, 'skuSupported': False, 'addressSupported': 0, 'isCod': 0, 'consigneeName': None, 'pickVOname': None, 'shipmentType': 0, 'retTag': 0, 'tagSource': 0, 'userDefinedTag': None, 'newProvinceId': 0, 'newCityId': 0, 'newCountyId': 0, 'newTownId': 0, 'newProvinceName': None, 'used': False, 'address': None, 'name': 'xx', 'message': None, 'id': 0}, 'msgUuid': None, 'message': 'xxxxxx商品无货'}
|
|
|
|
# 下单成功
|
|
|
|
# {'overSea': False, 'orderXml': None, 'cartXml': None, 'noStockSkuIds': '', 'reqInfo': None, 'hasJxj': False, 'addedServiceList': None, 'sign': None, 'pin': 'xxx', 'needCheckCode': False, 'success': True, 'resultCode': 0, 'orderId': 8740xxxxx, 'submitSkuNum': 1, 'deductMoneyFlag': 0, 'goJumpOrderCenter': False, 'payInfo': None, 'scaleSkuInfoListVO': None, 'purchaseSkuInfoListVO': None, 'noSupportHomeServiceSkuList': None, 'msgMobile': None, 'addressVO': None, 'msgUuid': None, 'message': None}
|
|
|
|
|
|
|
|
if resp_json.get('success'):
|
|
|
|
logger.info('订单提交成功! 订单号:%s', resp_json.get('orderId'))
|
|
|
|
sendMessage('订单提交成功! 订单号:{}'.format(resp_json.get('orderId')))
|
|
|
|
sys.exit(1)
|
|
|
|
return True
|
|
|
|
else:
|
|
|
|
message, result_code = resp_json.get('message'), resp_json.get('resultCode')
|
|
|
|
if result_code == 0:
|
|
|
|
# self._save_invoice()
|
|
|
|
message = message + '(下单商品可能为第三方商品,将切换为普通发票进行尝试)'
|
|
|
|
elif result_code == 60077:
|
|
|
|
message = message + '(可能是购物车为空 或 未勾选购物车中商品)'
|
|
|
|
elif result_code == 60123:
|
|
|
|
message = message + '(需要在payment_pwd参数配置支付密码)'
|
|
|
|
logger.info('订单提交失败, 错误码:%s, 返回信息:%s', result_code, message)
|
|
|
|
logger.info(resp_json)
|
|
|
|
return False
|
|
|
|
except Exception as e:
|
|
|
|
logger.error(e)
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
'''
|
|
|
|
购买环节
|
|
|
|
测试三次
|
|
|
|
'''
|
|
|
|
|
|
|
|
|
|
|
|
def buyMask(self, sku_id):
|
|
|
|
retry = eval(global_config.getRaw("config", "retry"))
|
|
|
|
for count in range(retry):
|
|
|
|
logger.info('第[%s/%s]次尝试提交订单', count, 3)
|
|
|
|
self.cancel_select_all_cart_item()
|
|
|
|
cart = self.cart_detail()
|
|
|
|
if sku_id in cart:
|
|
|
|
logger.info('%s 已在购物车中,调整数量为 %s', sku_id, 1)
|
|
|
|
cart_item = cart.get(sku_id)
|
|
|
|
self.change_item_num_in_cart(
|
|
|
|
sku_id=sku_id,
|
|
|
|
vender_id=cart_item.get('vender_id'),
|
|
|
|
num=1,
|
|
|
|
p_type=cart_item.get('p_type'),
|
|
|
|
target_id=cart_item.get('target_id'),
|
|
|
|
promo_id=cart_item.get('promo_id')
|
|
|
|
)
|
|
|
|
else:
|
|
|
|
self.add_item_to_cart(sku_id)
|
|
|
|
risk_control = self.get_checkout_page_detail()
|
|
|
|
if risk_control == '刷新太频繁了':
|
|
|
|
return False
|
|
|
|
if len(risk_control) > 0:
|
|
|
|
if self.submit_order(risk_control):
|
|
|
|
return True
|
|
|
|
logger.info('休息%ss', 3)
|
|
|
|
time.sleep(3)
|
|
|
|
else:
|
|
|
|
logger.info('执行结束,提交订单失败!')
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
'''
|
|
|
|
查询库存
|
|
|
|
'''
|
|
|
|
|
|
|
|
'''
|
|
|
|
update by rlacat
|
|
|
|
解决skuid长度过长(超过99个)导致无法查询问题
|
|
|
|
'''
|
|
|
|
def check_stock(self):
|
|
|
|
st_tmp = []
|
|
|
|
len_arg = 70
|
|
|
|
#print("skustr:",skuidStr)
|
|
|
|
#print("skuids:",len(skuids))
|
|
|
|
skuid_nums = len(self.skuids)
|
|
|
|
skuid_batchs = math.ceil(skuid_nums / len_arg)
|
|
|
|
#print("skuid_batchs:",skuid_batchs)
|
|
|
|
if(skuid_batchs > 1):
|
|
|
|
for i in range(0,skuid_batchs):
|
|
|
|
if(len_arg*(i+1) <= len(self.skuids)):
|
|
|
|
#print("取个数:",len_arg*i,"至",len_arg*(i+1))
|
|
|
|
skuidStr = ','.join(self.skuids[len_arg*i:len_arg*(i+1)])
|
|
|
|
st_tmp+=self.check_stock_tmp(skuidStr,self.skuids[len_arg*i:len_arg*(i+1)])
|
|
|
|
else:
|
|
|
|
#print("取个数:",len_arg*i,"至",len_arg*(i+1))
|
|
|
|
skuidStr = ','.join(self.skuids[len_arg*i:skuid_nums])#skuid配置的最后一段
|
|
|
|
#print(skuidStr)
|
|
|
|
st_tmp+=self.check_stock_tmp(skuidStr,self.skuids[len_arg*i:skuid_nums])
|
|
|
|
else:
|
|
|
|
#<=1的情况
|
|
|
|
skuidStr = ','.join(self.skuids)
|
|
|
|
st_tmp=self.check_stock_tmp(skuidStr,self.skuids)
|
|
|
|
return st_tmp
|
|
|
|
|
|
|
|
def check_stock_tmp(self, skuidString,skuids_a):
|
|
|
|
callback = 'jQuery' + str(random.randint(1000000, 9999999))
|
|
|
|
headers = {
|
|
|
|
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/531.36",
|
|
|
|
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3",
|
|
|
|
"Referer": "https://cart.jd.com/cart.action",
|
|
|
|
"Connection": "keep-alive",
|
|
|
|
}
|
|
|
|
url = 'https://c0.3.cn/stocks'
|
|
|
|
payload = {
|
|
|
|
'type': 'getstocks',
|
|
|
|
'skuIds': skuidString,
|
|
|
|
'area': self.area,
|
|
|
|
'callback': callback,
|
|
|
|
'_': int(time.time() * 1000),
|
|
|
|
}
|
|
|
|
resp = self.session.get(url=url, params=payload, headers=headers)
|
|
|
|
resptext = resp.text.replace(callback + '(', '').replace(')', '')
|
|
|
|
respjson = json.loads(resptext)
|
|
|
|
inStockSkuid = []
|
|
|
|
nohasSkuid = []
|
|
|
|
#print(resptext,respjson)
|
|
|
|
for i in skuids_a:
|
|
|
|
#print("当前处理:",i)
|
|
|
|
if(respjson[i]['StockStateName'] != '无货'):
|
|
|
|
inStockSkuid.append(i)
|
|
|
|
else:
|
|
|
|
nohasSkuid.append(i)
|
|
|
|
#print(nohasSkuid)
|
|
|
|
logger.info('[%s]无货', ','.join(nohasSkuid))
|
|
|
|
return inStockSkuid
|
|
|
|
|
|
|
|
@check_login
|
|
|
|
def buy(self):
|
|
|
|
sku_id = global_config.getRaw('config', 'sku_id')
|
|
|
|
retry = eval(global_config.getRaw("config", "retry"))
|
|
|
|
for count in range(retry):
|
|
|
|
logger.info('第[%s/%s]次尝试提交订单', count, retry)
|
|
|
|
self.cancel_select_all_cart_item()
|
|
|
|
cart = self.cart_detail()
|
|
|
|
if sku_id in cart:
|
|
|
|
logger.info('%s 已在购物车中,调整数量为 %s', sku_id, 1)
|
|
|
|
cart_item = cart.get(sku_id)
|
|
|
|
self.change_item_num_in_cart(
|
|
|
|
sku_id=sku_id,
|
|
|
|
vender_id=cart_item.get('vender_id'),
|
|
|
|
num=1,
|
|
|
|
p_type=cart_item.get('p_type'),
|
|
|
|
target_id=cart_item.get('target_id'),
|
|
|
|
promo_id=cart_item.get('promo_id')
|
|
|
|
)
|
|
|
|
else:
|
|
|
|
self.add_item_to_cart(sku_id)
|
|
|
|
risk_control = self.get_checkout_page_detail()
|
|
|
|
if risk_control == '刷新太频繁了':
|
|
|
|
return False
|
|
|
|
if len(risk_control) > 0:
|
|
|
|
if self.submit_order(risk_control):
|
|
|
|
return True
|
|
|
|
logger.info('休息%ss', 3)
|
|
|
|
time.sleep(3)
|
|
|
|
else:
|
|
|
|
logger.info('执行结束,提交订单失败!')
|
|
|
|
return False
|
|
|
|
|
|
|
|
@check_login
|
|
|
|
def initCart(self):
|
|
|
|
sku_id = global_config.getRaw('config', 'sku_id')
|
|
|
|
self.cancel_select_all_cart_item()
|
|
|
|
cart = self.cart_detail()
|
|
|
|
if sku_id in cart:
|
|
|
|
logger.info('%s 已在购物车中,调整数量为 %s', sku_id, 1)
|
|
|
|
cart_item = cart.get(sku_id)
|
|
|
|
self.change_item_num_in_cart(
|
|
|
|
sku_id=sku_id,
|
|
|
|
vender_id=cart_item.get('vender_id'),
|
|
|
|
num=1,
|
|
|
|
p_type=cart_item.get('p_type'),
|
|
|
|
target_id=cart_item.get('target_id'),
|
|
|
|
promo_id=cart_item.get('promo_id')
|
|
|
|
)
|
|
|
|
else:
|
|
|
|
self.add_item_to_cart(sku_id)
|
|
|
|
logger.info('购物车初始化结束,程序开始后请勿更改购物车')
|
|
|
|
|
|
|
|
@check_login
|
|
|
|
def fastBuy(self):
|
|
|
|
retry = eval(global_config.getRaw("config", "retry"))
|
|
|
|
for count in range(retry):
|
|
|
|
logger.info('第[%s/%s]次尝试提交订单', count, retry)
|
|
|
|
risk_control = self.get_checkout_page_detail()
|
|
|
|
if risk_control == -1:
|
|
|
|
continue
|
|
|
|
if risk_control == '刷新太频繁了':
|
|
|
|
return False
|
|
|
|
if len(risk_control) > 0:
|
|
|
|
if self.submit_order(risk_control):
|
|
|
|
return True
|
|
|
|
logger.info('休息%ss', 3)
|
|
|
|
time.sleep(3)
|
|
|
|
else:
|
|
|
|
logger.info('执行结束,提交订单失败!')
|
|
|
|
return False
|