Browse Source

refactor(framework): change framework to tinyserver

master v1.0.0-alpha
louisyoungx 3 years ago
parent
commit
8aa27b8fae
  1. 3
      Config/__init__.py
  2. 110
      Config/config.ini
  3. 19
      Config/settings.py
  4. 0
      Core/__init__.py
  5. 3
      Core/core.py
  6. 0
      Core/exception.py
  7. 0
      Core/login.py
  8. 296
      Core/spider.py
  9. 8
      Core/timer.py
  10. 6
      Core/util.py
  11. 3
      Logger/__init__.py
  12. 64
      Logger/logger.py
  13. 3
      Message/__init__.py
  14. 49
      Message/message.py
  15. 103
      README.en.md
  16. 3
      Scheduler/__init__.py
  17. 157
      Scheduler/scheduler.py
  18. 0
      Server/__init__.py
  19. 34
      Server/api.py
  20. 127
      Server/handler.py
  21. 24
      Server/server.py
  22. 7
      Server/url.py
  23. 1
      Static/css/element_index.css
  24. BIN
      Static/css/fonts/element-icons.woff
  25. 33
      Static/css/index.css
  26. 180
      Static/index.html
  27. 1
      Static/js/element_index.js
  28. 11965
      Static/js/vue.js
  29. BIN
      Static/title.ico
  30. 0
      TEST/Seckill.py
  31. 0
      TEST/area_id/1.北京.txt
  32. 0
      TEST/area_id/10.黑龙江.txt
  33. 0
      TEST/area_id/11.内蒙古.txt
  34. 0
      TEST/area_id/12.江苏.txt
  35. 0
      TEST/area_id/13.山东.txt
  36. 0
      TEST/area_id/14.安徽.txt
  37. 0
      TEST/area_id/15.浙江.txt
  38. 0
      TEST/area_id/16.福建.txt
  39. 0
      TEST/area_id/17.湖北.txt
  40. 0
      TEST/area_id/18.湖南.txt
  41. 0
      TEST/area_id/19.广东.txt
  42. 0
      TEST/area_id/2.上海.txt
  43. 0
      TEST/area_id/20.广西.txt
  44. 0
      TEST/area_id/21.江西.txt
  45. 0
      TEST/area_id/22.四川.txt
  46. 0
      TEST/area_id/23.海南.txt
  47. 0
      TEST/area_id/24.贵州.txt
  48. 0
      TEST/area_id/25.云南.txt
  49. 0
      TEST/area_id/26.西藏.txt
  50. 0
      TEST/area_id/27.陕西.txt
  51. 0
      TEST/area_id/28.甘肃.txt
  52. 0
      TEST/area_id/29.青海.txt
  53. 0
      TEST/area_id/3.天津.txt
  54. 0
      TEST/area_id/30.宁夏.txt
  55. 0
      TEST/area_id/31.新疆.txt
  56. 0
      TEST/area_id/32.台湾.txt
  57. 0
      TEST/area_id/4.重庆.txt
  58. 0
      TEST/area_id/5.河北.txt
  59. 0
      TEST/area_id/52993.港澳.txt
  60. 0
      TEST/area_id/53283.海外.txt
  61. 0
      TEST/area_id/6.山西.txt
  62. 0
      TEST/area_id/7.河南.txt
  63. 0
      TEST/area_id/8.辽宁.txt
  64. 0
      TEST/area_id/84.钓鱼岛.txt
  65. 0
      TEST/area_id/9.吉林.txt
  66. 0
      TEST/area_id/README.md
  67. 0
      TEST/area_id/get_area_id.py
  68. 0
      TEST/get_eid_fp.html
  69. 48
      config.ini
  70. 23
      logger.py
  71. 17
      main.py
  72. 77
      message.py
  73. 42
      runserver.py

3
Config/__init__.py

@ -0,0 +1,3 @@ @@ -0,0 +1,3 @@
'''
设置模块
'''

110
Config/config.ini

@ -0,0 +1,110 @@ @@ -0,0 +1,110 @@
[Spider]
# eid, fp参数必须填写,具体请参考Wiki-常见问题 https://github.com/tychxn/jd-assistant/wiki/1.-%E4%BA%AC%E4%B8%9C%E6%8A%A2%E8%B4%AD%E5%8A%A9%E6%89%8B%E7%94%A8%E6%B3%95
eid = "2PBCMB2WINZUQI6XRDCSKOZXTCHEOHLDABNAVSAEYTS6DBH2DWOACQPUEKXGHV7ZYTKCRDXZX34SPL2XI3KQYVNVSM"
fp = "63a2c6fceb97a082753cdf00710f4f46"
# area到area_id目录下人工查找,例如:江西省南昌市昌北区蛟桥镇的area是21_1827_4101_40925
area = 21_1827_4101_40925
# 商品id
sku_id = 10027576824361
# 购买数量
amount = 1
# 设定时间 # 2020-12-09 10:00:00.100000(修改成每天的几点几分几秒几毫秒)
buy_time = 2021-11-13 04:30:00.000
# 提交订单失败重试次数
retry = 100
# 查询库存请求超时(秒),可选配置,默认10秒
timeout = 10
# 默认UA
DEFAULT_USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36"
# 是否使用随机 useragent,默认为 false
random_useragent = false
# 多进程数量
work_count = 1
[Account]
# 支付密码
# 如果你的账户中有可用的京券(注意不是东券)或 在上次购买订单中使用了京豆,
# 那么京东可能会在下单时自动选择京券支付 或 自动勾选京豆支付。
# 此时下单会要求输入六位数字的支付密码。请在下方配置你的支付密码,如 123456 。
# 如果没有上述情况,下方请留空。
payment_pwd = ""
[Message]
# 使用了Server酱的推送服务
# 如果想开启下单成功后消息推送,则将 enable 设置为 true,默认为 false 不开启推送
# 开启消息推送必须填入 sckey,如何获取请参考 http://sc.ftqq.com/3.version。感谢Server酱~
enable = false
sckey = ???????
# =============================
# =============================
# ====== 以下框架配置请勿动 ======
# =============================
# =============================
[Information]
# 本项目的信息
# PROJECT-项目名,VERSION-版本,AUTHOR-作者,TIME-创作时间
PROJECT = "jd-shopper"
VERSION = "1.0.0-Alpha"
AUTHOR = "Louis·Young"
TIME = "2021-11-13"
[Debug]
# DEBUG 设置
# DEBUG-开启DEBUG后无视Scheduler只会执行Core.core.main函数中代码
DEBUG = False
[Server]
# 服务器信息
# SERVER_NAME-服务器名,SERVER_VERSION-服务器版本
# START_USING-是否开启服务器
# SERVER_HOST-填写服务器域名,LOCAL_HOST-用于启动项目的局域网IP,PORT-端口
# PROCESS_MODEL-是否开启多进程,PROCESS_COUNT-进程数量
SERVER_NAME = "TinyServer"
SERVER_VERSION = "0.4.8"
START_USING = True
SERVER_HOST = "www.server.com"
LOCAL_HOST = "0.0.0.0"
PORT = 12020
PROCESS_MODEL = False
PROCESS_COUNT = 4
[Logger]
# 记录设置
# FILE_NAME-记录文件名,AMOUNT-记录文件个数,MAX_BYTES-单个记录文件的大小
# CLEAR_UP每次启动程序是否清理log文件中内容,SERVER_LOGGER-开启HTTP Server的log记录
FILE_NAME = "TinyServerManager.log"
FILE_PATH = "/Logger/Log_Files/"
AMOUNT = 1
MAX_BYTES = 10485760
CLEAR_UP = True
SERVER_LOGGER = False
[Scheduler]
# 调度器设置(用于定时执行)
# START_USING-是否开启定时执行,调度器开启后main函数将被scheduler调度器代理,开启定时执行main
# START_TIME-任务开启时间,SKIP_WEEKEND-周末不执行任务
START_USING = False
START_TIME = "18:00:00"
SKIP_WEEKEND = True

19
config.py → Config/settings.py

@ -1,10 +1,10 @@ @@ -1,10 +1,10 @@
import os
import configparser
class Config(object):
def __init__(self, config_file='config.ini'):
self._path = os.path.join(os.getcwd(), config_file)
self.rootPath = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
self._path = self.rootPath + "/Config/" + config_file
if not os.path.exists(self._path):
raise FileNotFoundError("No such file: config.ini")
self._config = configparser.ConfigParser()
@ -12,17 +12,14 @@ class Config(object): @@ -12,17 +12,14 @@ class Config(object):
self._configRaw = configparser.RawConfigParser()
self._configRaw.read(self._path, encoding='utf-8-sig')
def get(self, section, name):
return self._config.get(section, name)
def settings(self, section, name):
return eval(self._config.get(section, name))
def getRaw(self, section, name):
def raw(self, section, name):
return self._configRaw.get(section, name)
def setMode(self, mode=''):
self.mode = mode
def getMode(self):
return self.mode
def path(self):
return self.rootPath
global_config = Config()
config = Config()

0
Core/__init__.py

3
Core/core.py

@ -0,0 +1,3 @@ @@ -0,0 +1,3 @@
def main():
# 此处填写业务
print("===== 此处业务处理 =====")

0
exception.py → Core/exception.py

0
login.py → Core/login.py

296
WaitingAndBuy.py → Core/spider.py

@ -9,28 +9,47 @@ import sys @@ -9,28 +9,47 @@ 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 Config.settings import config
from Logger.logger import logger
from login import SpiderSession, QrLogin
from message import sendMessage
from Message.message import sendMessage
from timer import Timer
from util import parse_json
class Waiter():
def __init__(self):
def __init__(self,
skuids=config.global_config.getRaw("Spider", "sku_id"),
area=config.global_config.getRaw("Spider", "area"),
eid=config.global_config.getRaw("Spider", "eid"),
fp=config.global_config.getRaw("Spider", "fp"),
count=config.global_config.getRaw("Spider", "amount"),
payment_pwd=config.global_config.getRaw(
"Account", "payment_pwd"),
retry=eval(config.global_config.getRaw("Spider", "retry")),
work_count=eval(config.global_config.getRaw(
'Spider', 'work_count')),
timeout=float(config.global_config.getRaw(
"Spider", "timeout"))
):
self.skuids = skuids
self.area = area
self.eid = eid
self.fp = fp
self.count = count
self.payment_pwd = payment_pwd
self.retry = retry
self.work_count = work_count
self.timeout = timeout
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.item_cat = {}
self.item_vender_ids = {} # 记录商家id
self.headers = {'User-Agent': self.user_agent}
self.timers = Timer()
@ -55,7 +74,7 @@ class Waiter(): @@ -55,7 +74,7 @@ class Waiter():
"""
用户登陆态校验装饰器若用户未登陆则调用扫码登陆
"""
@functools.wraps(func)
@ functools.wraps(func)
def new_func(self, *args, **kwargs):
if not self.qrlogin.is_login:
logger.info("{0} 需登陆后调用,开始扫码登陆".format(func.__name__))
@ -101,31 +120,34 @@ class Waiter(): @@ -101,31 +120,34 @@ class Waiter():
def get_sku_title(self):
"""获取商品名称"""
url = 'https://item.jd.com/{}.html'.format(global_config.getRaw('config', 'sku_id'))
url = 'https://item.jd.com/{}.html'.format(self.skuids)
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]
try:
result = sku_title[0]
return result
except:
return self.get_sku_title()
@check_login
@ check_login
def waitAndBuy_by_proc_pool(self):
"""
多进程进行抢购
work_count进程数量
"""
work_count = eval(global_config.getRaw('settings', 'work_count'))
work_count = self.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}'
"""
检查是否有货
"""
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)
@ -149,23 +171,26 @@ class Waiter(): @@ -149,23 +171,26 @@ class Waiter():
def _waitForSell(self):
area_id = self.area
sku_id = self.skuids
logger.info("正在等待商品上架:{}".format(self.get_sku_title()[:80] + " ......"))
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] + " ......"))
logger.info("商品上架: {}".format(
self.get_sku_title()[:80] + " ......"))
# self.waitAndBuy_by_proc_pool()
self.buy()
else:
logger.info("等待商品上架: {}".format(
self.get_sku_title()[:80] + " ......"))
time.sleep(random.randint(1, 10))
def _waitTimeForSell(self):
self.initCart()
logger.info("正在等待商品上架:{}".format(self.get_sku_title()[:80] + " ......"))
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
@ -189,11 +214,13 @@ class Waiter(): @@ -189,11 +214,13 @@ class Waiter():
resp_text = ''
try:
resp_text = requests.get(url=url, params=payload, headers=headers, timeout=self.timeout).text
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 -- 可配货
# 商品库存状态:33 -- 现货 0,34 -- 无货 36 -- 采购中 40 -- 可配货
stock_state = stock_info.get('StockState')
if sku_state == 1 and stock_state in (33, 40):
logger.info("有货: " + stock_info.get('StockStateName'))
return True
@ -207,69 +234,14 @@ class Waiter(): @@ -207,69 +234,14 @@ class Waiter():
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)
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,
@ -286,7 +258,6 @@ class Waiter(): @@ -286,7 +258,6 @@ class Waiter():
勾选购物车中的所有商品
'''
def select_all_cart_item(self):
url = "https://cart.jd.com/selectAllItem.action"
data = {
@ -300,12 +271,10 @@ class Waiter(): @@ -300,12 +271,10 @@ class Waiter():
return False
return True
'''
删除购物车选中商品
'''
def remove_item(self):
url = "https://cart.jd.com/batchRemoveSkusFromCart.action"
data = {
@ -337,7 +306,6 @@ class Waiter(): @@ -337,7 +306,6 @@ class Waiter():
购物车详情
'''
def cart_detail(self):
url = 'https://cart.jd.com/cart.action'
headers = {
@ -362,14 +330,18 @@ class Waiter(): @@ -362,14 +330,18 @@ class Waiter():
# ['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
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')), # 商品名称
# 商品名称
'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:], # 总价
# 单价
'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,
@ -381,12 +353,10 @@ class Waiter(): @@ -381,12 +353,10 @@ class Waiter():
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 = {
@ -410,17 +380,15 @@ class Waiter(): @@ -410,17 +380,15 @@ class Waiter():
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,
'pcount': self.count,
'ptype': 1,
}
resp = self.session.get(url=url, params=payload)
@ -428,14 +396,14 @@ class Waiter(): @@ -428,14 +396,14 @@ class Waiter():
result = True
else: # 普通商品成功加入购物车后会跳转到提示 "商品已成功加入购物车!" 页面
soup = BeautifulSoup(resp.text, "html.parser")
result = bool(soup.select('h3.ftx-02')) # [<h3 class="ftx-02">商品已成功加入购物车!</h3>]
# [<h3 class="ftx-02">商品已成功加入购物车!</h3>]
result = bool(soup.select('h3.ftx-02'))
if result:
logger.info('%s 已成功加入购物车', sku_id)
else:
logger.error('%s 添加到购物车失败', sku_id)
def get_checkout_page_detail(self):
"""获取订单结算页面信息
@ -456,29 +424,36 @@ class Waiter(): @@ -456,29 +424,36 @@ class Waiter():
'Host': 'trade.jd.com',
}
try:
# print(url)
resp = self.session.get(url=url, params=payload, headers=headers)
if "刷新太频繁了" in resp.text:
logger.error("刷新太频繁了 url: {}".format(url))
return ''
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')
# print(soup.title)
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
# remove '寄送至: ' from the begin
'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:],
'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
logger.error('该商品频繁刷新被京东风控!!!(仅限该商品,请勿重复多次下单,易被风控)')
return ''
def submit_order(self, risk_control):
"""提交订单
@ -513,7 +488,8 @@ class Waiter(): @@ -513,7 +488,8 @@ class Waiter():
'riskControl': risk_control,
'submitOrderParam.isBestCoupon': 1,
'submitOrderParam.jxj': 1,
'submitOrderParam.trackId': '9643cbd55bbbe103eef18a213e069eb0', # Todo: need to get trackId
# Todo: need to get trackId
'submitOrderParam.trackId': '9643cbd55bbbe103eef18a213e069eb0',
# 'submitOrderParam.eid': eid,
# 'submitOrderParam.fp': fp,
'submitOrderParam.needCheck': 1,
@ -521,9 +497,10 @@ class Waiter(): @@ -521,9 +497,10 @@ class Waiter():
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)
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",
@ -535,6 +512,9 @@ class Waiter(): @@ -535,6 +512,9 @@ class Waiter():
try:
resp = self.session.post(url=url, data=data, headers=headers)
if "刷新太频繁了" in resp.text:
logger.error("刷新太频繁了 url: {}".format(url))
return False
resp_json = json.loads(resp.text)
# 返回信息示例:
@ -553,10 +533,11 @@ class Waiter(): @@ -553,10 +533,11 @@ class Waiter():
sys.exit(1)
return True
else:
message, result_code = resp_json.get('message'), resp_json.get('resultCode')
message, result_code = resp_json.get(
'message'), resp_json.get('resultCode')
if result_code == 0:
# self._save_invoice()
message = message + '(下单商品可能为第三方商品,将切换为普通发票进行尝试)'
message = message + '<<<<<<<<<<京东返回的限制信息<<<<<<<-------该商品被京东限制购买'
elif result_code == 60077:
message = message + '(可能是购物车为空 或 未勾选购物车中商品)'
elif result_code == 60123:
@ -568,18 +549,13 @@ class Waiter(): @@ -568,18 +549,13 @@ class Waiter():
logger.error(e)
return False
'''
购买环节
测试三次
'''
def buyMask(self, sku_id):
retry = eval(global_config.getRaw("config", "retry"))
retry = self.retry
for count in range(retry):
logger.info('第[%s/%s]次尝试提交订单', count, 3)
self.cancel_select_all_cart_item()
@ -590,7 +566,7 @@ class Waiter(): @@ -590,7 +566,7 @@ class Waiter():
self.change_item_num_in_cart(
sku_id=sku_id,
vender_id=cart_item.get('vender_id'),
num=1,
num=self.skuids,
p_type=cart_item.get('p_type'),
target_id=cart_item.get('target_id'),
promo_id=cart_item.get('promo_id')
@ -609,7 +585,6 @@ class Waiter(): @@ -609,7 +585,6 @@ class Waiter():
logger.info('执行结束,提交订单失败!')
return False
'''
查询库存
'''
@ -618,32 +593,37 @@ class Waiter(): @@ -618,32 +593,37 @@ class Waiter():
update by rlacat
解决skuid长度过长超过99个导致无法查询问题
'''
def check_stock(self):
st_tmp = []
len_arg = 70
#print("skustr:",skuidStr)
#print("skuids:",len(skuids))
# 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)])
# 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])
# 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的情况
# <=1的情况
skuidStr = ','.join(self.skuids)
st_tmp=self.check_stock_tmp(skuidStr,self.skuids)
st_tmp = self.check_stock_tmp(skuidStr, self.skuids)
return st_tmp
def check_stock_tmp(self, skuidString,skuids_a):
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",
@ -664,32 +644,32 @@ class Waiter(): @@ -664,32 +644,32 @@ class Waiter():
respjson = json.loads(resptext)
inStockSkuid = []
nohasSkuid = []
#print(resptext,respjson)
# print(resptext,respjson)
for i in skuids_a:
#print("当前处理:",i)
if(respjson[i]['StockStateName'] != '无货'):
# print("当前处理:",i)
if (respjson[i]['StockStateName'] != '无货'):
inStockSkuid.append(i)
else:
nohasSkuid.append(i)
#print(nohasSkuid)
# 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"))
sku_id = self.skuids
retry = self.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)
logger.info('%s 已在购物车中,调整数量为 %s', sku_id, self.count)
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,
num=self.skuids,
p_type=cart_item.get('p_type'),
target_id=cart_item.get('target_id'),
promo_id=cart_item.get('promo_id')
@ -710,16 +690,16 @@ class Waiter(): @@ -710,16 +690,16 @@ class Waiter():
@check_login
def initCart(self):
sku_id = global_config.getRaw('config', 'sku_id')
sku_id = self.skuids
self.cancel_select_all_cart_item()
cart = self.cart_detail()
if sku_id in cart:
logger.info('%s 已在购物车中,调整数量为 %s', sku_id, 1)
logger.info('%s 已在购物车中,调整数量为 %s', sku_id, self.count)
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,
num=self.count,
p_type=cart_item.get('p_type'),
target_id=cart_item.get('target_id'),
promo_id=cart_item.get('promo_id')
@ -730,7 +710,7 @@ class Waiter(): @@ -730,7 +710,7 @@ class Waiter():
@check_login
def fastBuy(self):
retry = eval(global_config.getRaw("config", "retry"))
retry = self.retry
for count in range(retry):
logger.info('第[%s/%s]次尝试提交订单', count, retry)
risk_control = self.get_checkout_page_detail()
@ -741,8 +721,8 @@ class Waiter(): @@ -741,8 +721,8 @@ class Waiter():
if len(risk_control) > 0:
if self.submit_order(risk_control):
return True
logger.info('休息%ss', 3)
time.sleep(3)
logger.info('休息%ss', 5)
time.sleep(5)
else:
logger.info('执行结束,提交订单失败!')
return False
return False

8
timer.py → Core/timer.py

@ -4,15 +4,15 @@ import requests @@ -4,15 +4,15 @@ import requests
import json
from datetime import datetime
from logger import logger
from config import global_config
from Logger.logger import logger
from Config.settings import config
class Timer(object):
def __init__(self, sleep_interval=0.5):
# '2018-09-28 22:45:50.000'
# buy_time = 2020-12-22 09:59:59.500
buy_time_everyday = global_config.getRaw('config', 'buy_time').__str__()
buy_time_everyday = config.global_config.getRaw('config', 'buy_time').__str__()
localtime = time.localtime(time.time())
#self.buy_time = datetime.strptime(
# localtime.tm_year.__str__() + '-' + localtime.tm_mon.__str__() + '-' + localtime.tm_mday.__str__()
@ -52,7 +52,7 @@ class Timer(object): @@ -52,7 +52,7 @@ class Timer(object):
def start(self):
logger.info('正在等待到达设定时间:{}'.format(self.buy_time))
logger.info('正检测本地时间与京东服务器时间误差为【{}】毫秒'.format(self.diff_time))
mode = global_config.getMode()
mode = config.global_config.getMode()
if mode != '3':
while True:
# 本地时间减去与京东的时间差,能够将时间误差提升到0.1秒附近

6
util.py → Core/util.py

@ -4,7 +4,7 @@ import requests @@ -4,7 +4,7 @@ import requests
import os
import time
from config import global_config
from Config.settings import config
USER_AGENTS = [
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36",
@ -79,13 +79,13 @@ def wait_some_time(): @@ -79,13 +79,13 @@ def wait_some_time():
def send_wechat(message):
"""推送信息到微信"""
url = 'http://sc.ftqq.com/{}.send'.format(global_config.getRaw('messenger', 'sckey'))
url = 'http://sc.ftqq.com/{}.send'.format(config.global_config.getRaw('messenger', 'sckey'))
payload = {
"text":'抢购结果',
"desp": message
}
headers = {
'User-Agent':global_config.getRaw('config', 'DEFAULT_USER_AGENT')
'User-Agent':config.global_config.getRaw('config', 'DEFAULT_USER_AGENT')
}
requests.get(url, params=payload, headers=headers)

3
Logger/__init__.py

@ -0,0 +1,3 @@ @@ -0,0 +1,3 @@
'''
日志模块
'''

64
Logger/logger.py

@ -0,0 +1,64 @@ @@ -0,0 +1,64 @@
import os
import logging
import logging.handlers
from Config.settings import config
# 从config中查询所需数据
path = config.path() + config.settings("Logger", "FILE_PATH")
filename = config.path() + config.settings("Logger", "FILE_PATH") + config.settings("Logger", "FILE_NAME")
maxBytes = config.settings("Logger", "MAX_BYTES")
backupCount = config.settings("Logger", "AMOUNT")
clearUp = config.settings("Logger", "CLEAR_UP")
# 创建一个logger,创建一个列表存放logger数据
logger = logging.getLogger()
logger_records = []
class CustomFilter(logging.Filter):
def filter(self, record):
# logger_records.append(record.msg)
return record.msg
def clearUpLogFile():
if not os.path.exists(path):
os.mkdir(path)
with open(filename, "w") as file:
file.seek(0)
file.truncate() # 清空文件
def logger_init():
# 清理log文件
if clearUp:
clearUpLogFile()
# 设置logger输出级别
logger.setLevel(logging.INFO)
filter = CustomFilter()
logger.addFilter(filter)
# 设置logger输出格式
# fmt = logging.Formatter('%(asctime)s - %(process)d-%(threadName)s - %(pathname)s[line:%(lineno)d] - %(levelname)s: %(message)s')
fmt = '[%(asctime)s] (%(module)s.%(funcName)s): <%(levelname)s> %(message)s'
datefmt = "%Y-%m-%d %H:%M:%S"
formatter = logging.Formatter(fmt=fmt, datefmt=datefmt)
# 定义一个console_handler,用于输出到控制台
console_handler = logging.StreamHandler()
# 定义一个console_handler,用于输出到控制台
file_handler = logging.handlers.RotatingFileHandler(
filename, maxBytes=maxBytes, backupCount=backupCount, encoding="utf-8")
# 给handler添加formatter
console_handler.setFormatter(formatter)
file_handler.setFormatter(formatter)
# 把初始化完毕的handler对象添加到logger对象中
logger.addHandler(console_handler)
logger.addHandler(file_handler)
logger_init()

3
Message/__init__.py

@ -0,0 +1,3 @@ @@ -0,0 +1,3 @@
'''
消息模块
'''

49
Message/message.py

@ -0,0 +1,49 @@ @@ -0,0 +1,49 @@
#!/usr/bin/env python
# -*- encoding=utf8 -*-
import datetime
import json
import requests
from logger import logger
from config import global_config
class Messenger(object):
"""消息推送类"""
def __init__(self, sc_key):
if not sc_key:
raise Exception('sc_key can not be empty')
self.sc_key = sc_key
def send(self, text, desp=''):
if not text.strip():
logger.error('Text of message is empty!')
return
now_time = str(datetime.datetime.now())
desp = '[{0}]'.format(now_time) if not desp else '{0} [{1}]'.format(desp, now_time)
try:
resp = requests.get(
'https://sc.ftqq.com/{}.send?text={}&desp={}'.format(self.sc_key, text, desp)
)
resp_json = json.loads(resp.text)
if resp_json.get('errno') == 0:
logger.info('Message sent successfully [text: %s, desp: %s]', text, desp)
else:
logger.error('Fail to send message, reason: %s', resp.text)
except requests.exceptions.RequestException as req_error:
logger.error('Request error: %s', req_error)
except Exception as e:
logger.error('Fail to send message [text: %s, desp: %s]: %s', text, desp, e)
sckey = global_config.getRaw("messenger", "sckey")
message = Messenger(sckey)
def sendMessage(mes):
message.send(mes)

103
README.en.md

@ -0,0 +1,103 @@ @@ -0,0 +1,103 @@
# TinyServer
#### 1. Description
Local Python projects run the basic framework, no need to install third party libraries, built-in pure Python native implementation of multiprocess HTTP server, through the built-in RESTful Web API or web page to view the local log, can be timed to execute code, can be notifications, can define server API to view the running status, Customizable Web templates.
#### 2. Key Feature
- Provides logging, configuration management for running Python projects on the server
- Web page view log
- RESTful APIs provide an interface to project information
- Periodically execute the module, executing /Core/main.py on a regular basis based on the start and end time
- Message notification module, send email or cooperate with Mirai framework to send QQ messages
- Server API
- Program information and system status can be queried through the built-in API
- Customizable simple API
- Custom web templates
#### 3. Basic Modules
- Core
- main. py - Entry to program execution
- Config
- config.ini - Fill in the basic configuration information
- settings.py - Read and initialize data in config.ini
- Logger
- logger - Outputs log messages to the console, log file, and Server module
- Message
- message - Message passing interface, can send messages through QQ robot and mailbox
- Scheduler
- Scheduler - Scheduled execution of the /Core/main.py module. Once opened and set in config.ini, the /Core/main.py module is scheduled to be executed
- Server
- Handler - Contains the main HTTP request handling and API
- server - Used to configure and start the server thread
- Static
- Web page view log
- RESTful APIs provide an interface to project information
#### 4. Operation Environment
- [Python 3](https://www.python.org/)
#### 5. Installation Tutorial
1. ```shell
git clone https://gitee.com/louisyoung1/tiny-server.git
```
2. ```sh
cd tiny-server
```
#### 6. Usage
1. Change the code in /Core/main.py to the code you want to run
2. Edit the configuration items in /Config/config.ini file according to the comment requirements
3. Make sure you are in /tiny-server and enter
```sh
python3 runserver
```
#### 7. Directory Structure
```shell
.
├── Config
   ├── config.ini
   └── settings.py
├── Core
   └── core.py
├── Logger
   ├── Log_Files
     └── TinyServer.log
   └── logger.py
├── Message
   └── message.py
├── Scheduler
   ├── scheduler.py
   └── tools.py
├── Server
   ├── handler.py
   └── server.py
├── Static
   ├── 404.html
   ├── change.html
   ├── css
   ├── favicon.ico
   ├── images
   ├── index.html
   ├── js
   └── log.html
└── runserver.py
```
#### 8. Contribution
1. Fork the repository
2. Create Feat_xxx branch
3. Commit your code
4. Create Pull Request

3
Scheduler/__init__.py

@ -0,0 +1,3 @@ @@ -0,0 +1,3 @@
'''
消息模块
'''

157
Scheduler/scheduler.py

@ -0,0 +1,157 @@ @@ -0,0 +1,157 @@
import datetime
import time
from Logger.logger import logger
class Timer(object):
def __init__(self, task, startTime, skipWeekend):
'''初始化'''
self.task = task
self.start_time = startTime
self.skip_weekend = skipWeekend
def schedule(self):
'''调度执行上下文'''
while True: # 一个循环为一天时间
self._schedule() # 进入今天的循环
self.sleepToTomorrow() # 今天的任务结束,休眠到下一天
def _schedule(self):
'''调度执行'''
logger.info("Daily Task Initialized Successfully")
if self.skip_weekend and not self.isTodayWorkday():
# 今天不是工作日,结束今天的任务
return False
elif self.isTimePass():
# 今天的任务时间已经过了,结束今天的任务
return False
else:
self.execute()
return True
def execute(self):
real_datetime = self.realDate() # 当前的时间(日期)
real_mstime = self.dateMSTime(real_datetime) # 当前的时间(毫秒)
today_task_datetime = self.todayTaskTime() # 今天任务时间(日期)
today_task_mstime = self.dateMSTime(today_task_datetime) # 今天任务时间(毫秒)
wait_time = today_task_mstime - real_mstime # 获取当前时间与任务的时间差
logger.info("Waiting to Start Mission -> {}".format(today_task_datetime))
time.sleep(wait_time) # 线程休眠阻塞任务
self.task() # 阻塞结束执行
logger.info("Today's Mission Completed")
def realDate(self):
'''
获取当前的日期与时间
return: date "%Y-%m-%d %H:%M:%S"
'''
localtime = time.localtime(time.time())
date = \
localtime.tm_year.__str__() + '-' + \
localtime.tm_mon.__str__() + '-' + \
localtime.tm_mday.__str__() + ' ' + \
localtime.tm_hour.__str__() + ':' + \
localtime.tm_min.__str__() + ':' + \
localtime.tm_sec.__str__()
return date
def realMSTime(self):
'''
获取当前的毫秒时间
return: 毫秒时间
'''
return time.time()
def tomorrowMSTime(self):
'''
获取明天的00:00:00毫秒时间
return: 毫秒时间
'''
localtime = time.localtime(time.time())
# 今天00:00:00的日期时间
today_start_date = \
localtime.tm_year.__str__() + '-' + \
localtime.tm_mon.__str__() + '-' + \
localtime.tm_mday.__str__() + ' ' + \
'00:00:00'
today_start_time = self.dateMSTime(today_start_date)
tomorrow_start_time = today_start_time + 60 * 60 * 24
return tomorrow_start_time
def isTodayWorkday(self):
'''
该日期是否为工作日
params:
date "%Y-%m-%d %H:%M:%S"
return:
工作日: True
休息日: False
'''
localtime = time.localtime(time.time())
week = localtime.tm_wday.__str__()
if week in (5, 6):
# 如果是休息日
logger.info("Over The Weekend")
return False
else:
# 如果是工作日
logger.info("Working Day")
return True
def sleepToTomorrow(self):
'''休眠到下一天'''
real_datetime = self.realDate() # 当前的时间(日期)
real_mstime = self.dateMSTime(real_datetime) # 当前的时间(毫秒)
tomorrow_mstime = self.tomorrowMSTime() # 明天0点的时间(毫秒)
diff_time = tomorrow_mstime - real_mstime # 现在到明天0点的毫秒时间
logger.info("Sleeping to tomorrow -> {} Seconds".format(diff_time))
time.sleep(diff_time)
def isTimePass(self):
'''
确认当前时间是否超过今天执行时间
return
True超时
False没超时
'''
real_datetime = self.realDate() # 当前的时间(日期)
real_mstime = self.dateMSTime(real_datetime) # 当前的时间(毫秒)
today_task_datetime = self.todayTaskTime() # 今天任务时间(日期)
today_task_mstime = self.dateMSTime(today_task_datetime) # 今天任务时间(毫秒)
if real_mstime > today_task_mstime:
logger.info("Time Pass - Now Time: {} TaskTime: {}".format(real_datetime, today_task_datetime))
return True
else:
logger.info("Time Waiting - RealTime: {}".format(real_datetime))
return False
def dateMSTime(self, date):
'''
获取该日期对应的毫秒时间
params:
date 格式为"%Y-%m-%d %H:%M:%S"
return:
ms_time 毫秒时间
'''
date_time = datetime.datetime.strptime(date, "%Y-%m-%d %H:%M:%S")
ms_time = int(time.mktime(date_time.timetuple()) + date_time.microsecond)
return ms_time
def todayTaskTime(self):
'''
获取今天的任务日期
return: today_date "%Y-%m-%d %H:%M:%S"
'''
localtime = time.localtime(time.time())
today_date = \
localtime.tm_year.__str__() + '-' + \
localtime.tm_mon.__str__() + '-' + \
localtime.tm_mday.__str__() + ' ' + \
self.start_time
return today_date

0
Server/__init__.py

34
Server/api.py

@ -0,0 +1,34 @@ @@ -0,0 +1,34 @@
import copy
import psutil
from Config.settings import config
def log(request):
file_path = config.path() + config.settings("Logger", "FILE_PATH") + config.settings("Logger", "FILE_NAME")
file_page_file = open(file_path, 'r')
return str(file_page_file.read())
def systemInfo(request):
info = {}
info['cpu_count'] = psutil.cpu_count() # CPU逻辑数量
info['cpu_percent'] = psutil.cpu_percent(interval=1) # CPU使用率
info['cpu_times'] = psutil.cpu_times() # CPU的用户/系统/空闲时间
info['virtual_memory'] = psutil.virtual_memory() # 物理内存
info['swap_memory'] = psutil.swap_memory() # 交换内存
info['disk_partitions'] = psutil.disk_partitions() # 磁盘分区信息
info['disk_partitions'] = psutil.disk_usage('/') # 磁盘使用情况
info['disk_partitions'] = psutil.disk_io_counters() # 磁盘IO
info['disk_partitions'] = psutil.net_io_counters() # 获取网络读写字节/包的个数
info['disk_partitions'] = psutil.net_if_addrs() # 获取网络接口信息
info['disk_partitions'] = psutil.net_if_stats() # 获取网络接口状态
# need sudo: info['disk_partitions'] = psutil.net_connections() # 获取当前网络连接信息
return info
def serverConfig(request):
appConfig = copy.deepcopy(config._config._sections)
for model in appConfig:
for item in appConfig[model]:
appConfig[model][item] = eval(appConfig[model][item])
value = appConfig[model][item]
# DEBUG print(model, item, value, type(value))
return appConfig

127
Server/handler.py

@ -0,0 +1,127 @@ @@ -0,0 +1,127 @@
import os, json
import time
from Logger.logger import logger
from http.server import BaseHTTPRequestHandler
from Config.settings import config
# Document https://docs.python.org/3.9/library/http.server.html
from Server.url import urls
class RequestHandler(BaseHTTPRequestHandler):
'''处理请求并返回页面'''
# 处理一个GET请求
def do_GET(self):
self.rootPath = config.path() + "/Static"
url = self.requestline[4:-9]
request_data = {} # 存放GET请求数据
try:
if url.find('?') != -1:
req = url.split('?', 1)[1]
url = url.split('?', 1)[0]
parameters = req.split('&')
for i in parameters:
key, val = i.split('=', 1)
request_data[key] = val
except:
logger.error("URL Format Error")
if (url == "/"):
self.home()
elif ("/api" in url):
self.api(url[4:], request_data)
else:
self.file(url)
def log_message(self, format, *args):
SERVER_LOGGER = config.settings("Logger", "SERVER_LOGGER")
if SERVER_LOGGER:
logger.info(format % args)
else:
pass
def home(self):
file_path = self.rootPath + "/index.html"
home_page_file = open(file_path, 'r')
content = str(home_page_file.read())
self.send_response(200)
self.send_header("Content-Type", "text/html")
self.send_header("Content-Length", str(len(content)))
self.end_headers()
self.wfile.write(content.encode())
def file(self, url):
file_name = url.split("/")[-1]
file_sys_path = self.rootPath + url[:-len(file_name)]
file_path = ""
for root, dirs, files in os.walk(file_sys_path):
for file in files:
if file == file_name:
file_path = os.path.join(root, file)
else:
continue
if file_path != "":
self.send_response(200)
if file_path == "":
# file_path = self.rootPath + "/404.html" # Hard Code
self.noFound()
elif file_name[-5:] == ".html":
self.send_header("Content-Type", "text/html")
elif file_name[-4:] == ".css":
self.send_header("Content-Type", "text/css")
elif file_name[-3:] == ".js":
self.send_header("Content-Type", "application/javascript")
elif file_name[-4:] == ".png": # 二进制文件
self.send_header("Content-Type", "img/png")
file_page_file = open(file_path, 'rb')
self.end_headers()
self.wfile.write(file_page_file.read())
return
elif file_name[-4:] == ".jpg": # 二进制文件
self.send_header("Content-Type", "img/jpg")
file_page_file = open(file_path, 'rb')
self.end_headers()
self.wfile.write(file_page_file.read())
return
elif file_name[-4:] == ".ico": # 二进制文件
self.send_header("Content-Type", "img/ico")
file_page_file = open(file_path, 'rb')
self.end_headers()
self.wfile.write(file_page_file.read())
return
file_page_file = open(file_path, 'r')
content = str(file_page_file.read())
self.send_header("Content-Length", str(len(content)))
self.end_headers()
self.wfile.write(content.encode())
def api(self, url, request_data):
# ----------------------------------------------------------------
# 此处写API
content = urls(url, request_data)
# ----------------------------------------------------------------
localtime = time.localtime(time.time())
date = \
localtime.tm_year.__str__() + '-' + \
localtime.tm_mon.__str__() + '-' + \
localtime.tm_mday.__str__() + ' ' + \
localtime.tm_hour.__str__() + ':' + \
localtime.tm_min.__str__() + ':' + \
localtime.tm_sec.__str__()
jsondict = {}
jsondict["data"] = content
jsondict["time"] = date
res = json.dumps(jsondict)
self.send_response(200)
self.send_header("Content-Type", "text/html")
self.send_header("Content-Length", str(len(res)))
self.end_headers()
self.wfile.write(res.encode())
def noFound(self):
self.file("/404.html")

24
Server/server.py

@ -0,0 +1,24 @@ @@ -0,0 +1,24 @@
import json
from http.server import HTTPServer
from Logger.logger import logger
from Server.handler import RequestHandler
from Config.settings import config
def server():
NAME = config.settings("Server", "SERVER_NAME")
VERSION = config.settings("Server", "SERVER_VERSION")
DEBUG = config.settings("Debug", "DEBUG")
LOCAL_HOST = config.settings("Server", "LOCAL_HOST")
SERVER_HOST = config.settings("Server", "SERVER_HOST")
PORT = config.settings("Server", "PORT")
if DEBUG == True:
name = LOCAL_HOST
else:
name = SERVER_HOST
port = PORT
host = LOCAL_HOST
serverAddress = (host, port)
logger.info("{}-{}".format(NAME, VERSION))
logger.info("http://{}:{}/".format(name, port))
server = HTTPServer(serverAddress, RequestHandler)
server.serve_forever()

7
Server/url.py

@ -0,0 +1,7 @@ @@ -0,0 +1,7 @@
from Server.api import log, systemInfo, serverConfig
def urls(url, request):
if (url == "/log"): return log(request)
elif (url == "/system-info"): return systemInfo(request)
elif (url == "/config"): return serverConfig(request)
else: return "No Response"

1
Static/css/element_index.css

File diff suppressed because one or more lines are too long

BIN
Static/css/fonts/element-icons.woff

Binary file not shown.

33
Static/css/index.css

@ -0,0 +1,33 @@ @@ -0,0 +1,33 @@
.data_dict, .data_dict_goods{
display: none;
}
#form_container{
width: 600px;
margin: 0 auto;
border: 1px solid gainsboro;
}
h1{
text-align: center;
margin: 30px 0;
padding-bottom: 20px;
}
#account_input, #password_input{
width: 300px;
}
#password_form{
width: 380px;
}
.goods_input{
width: 300px;
margin-right: 20px;
}
.num_select{
width: 120px;
}
#add_button{
margin-left: 150px;
margin-bottom: 30px;
}
#run_button{
margin-left: 30px;
}

180
Static/index.html

@ -0,0 +1,180 @@ @@ -0,0 +1,180 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>订单自动提交</title>
<link rel="Shortcut Icon" href="title.ico" type="image/x-icon" />
<link type="text/css" rel="stylesheet" href="css/element_index.css" />
<link type="text/css" rel="stylesheet" href="css/index.css" />
</head>
<body>
<div id="form_container">
<h1>订单自动提交</h1>
<el-form ref="form" :model="form" label-width="80px">
<el-form-item label="登录方式">
<el-radio v-model="radio_login" label="1" @change="LoginMode" border
>自动登录</el-radio
>
<el-radio v-model="radio_login" label="2" @change="LoginMode" border
>手动登录</el-radio
>
</el-form-item>
<el-form-item label="账号">
<el-input
id="account_input"
v-model="account_value"
placeholder="请输入账号"
:disabled="input_writeable"
></el-input>
</el-form-item>
<el-form-item label="密码" id="password_form">
<el-input
id="password_input"
v-model="password_value"
placeholder="请输入密码"
:disabled="input_writeable"
show-password
></el-input>
</el-form-item>
<el-form-item label="运行时间">
<el-select v-model="date_value" placeholder="选择日期(默认今天)">
<el-option
v-for="item in date_options"
:key="item.date_value"
:label="item.label"
:value="item.date_value"
>
</el-option>
</el-select>
<el-time-picker
v-model="time_value"
:picker-options="{}"
placeholder="选择时间(默认现在)"
>
</el-time-picker>
</el-form-item>
<goods_link></goods_link>
<div v-for="(d,index) in goods_link_counter" :key="index">
<goods_link></goods_link>
</div>
<el-button id="add_button" @click="addGoods">继续添加</el-button>
<el-button
id="run_button"
@click="changeRunStatus"
type="primary"
:class="run_status"
>开始运行</el-button
>
</el-form>
<ul class="data_dict">
<!--登录方式, 账号, 密码, 运行时间, url-->
<li class="is_manual">{{is_manual}}</li>
<li class="jd_conut">{{account_value}}</li>
<li class="jd_password">{{password_value}}</li>
<li class="run_date">{{date_value}}</li>
<li class="run_time">{{time_value}}</li>
</ul>
</div>
<template id="goods_link">
<el-form-item label="添加商品" id="goods_link_form">
<el-input
class="goods_input"
v-model="goods_url"
placeholder="商品链接"
></el-input>
<el-input-number
class="num_select"
v-model="num"
@change="handleChange"
:min="1"
:max="999"
label="数量"
></el-input-number>
<ul class="data_dict_goods">
<li class="goods_url">{{goods_url}}</li>
<li class="goods_num">{{num}}</li>
</ul>
</el-form-item>
</template>
</body>
<script src="js/vue.js"></script>
<script src="js/element_index.js"></script>
<script>
Vue.component("goods_link", {
template: "#goods_link",
data: function () {
return {
num: 1,
goods_url: "",
};
},
// props:{
// num:{
// type: Number,
// default: 1
// },
// goods_url: {
// type: String,
// default: ''
// }
// },
methods: {
handleChange(value) {},
},
});
new Vue({
el: "#form_container",
data: function () {
return {
account_value: "",
password_value: "",
radio_login: "1",
is_manual: 0,
input_writeable: false,
run_status: "wait",
goods_link_counter: [],
date_value: "",
date_options: [
{
date_value: "today",
label: "今天",
},
{
date_value: "tomorrow",
label: "明天",
},
],
// time_value: new Date(2016, 9, 10, 18, 40),
time_value: "",
form: {
name: "",
region: "",
},
};
},
methods: {
LoginMode: function (value) {
if (this.radio_login === "1" || this.radio_login === 1) {
this.is_manual = 0;
this.input_writeable = false;
} else {
this.is_manual = 1;
this.input_writeable = true;
}
},
addGoods: function () {
this.goods_link_counter.push({});
},
changeRunStatus: function () {
if (!this.date_value) {
this.date_value = "today";
}
if (!this.time_value) {
this.time_value = new Date();
}
this.run_status = "trigger_run";
},
},
});
</script>
</html>

1
Static/js/element_index.js

File diff suppressed because one or more lines are too long

11965
Static/js/vue.js

File diff suppressed because it is too large Load Diff

BIN
Static/title.ico

Binary file not shown.

After

Width:  |  Height:  |  Size: 894 B

0
Seckill.py → TEST/Seckill.py

0
area_id/1.北京.txt → TEST/area_id/1.北京.txt

0
area_id/10.黑龙江.txt → TEST/area_id/10.黑龙江.txt

0
area_id/11.内蒙古.txt → TEST/area_id/11.内蒙古.txt

0
area_id/12.江苏.txt → TEST/area_id/12.江苏.txt

0
area_id/13.山东.txt → TEST/area_id/13.山东.txt

0
area_id/14.安徽.txt → TEST/area_id/14.安徽.txt

0
area_id/15.浙江.txt → TEST/area_id/15.浙江.txt

0
area_id/16.福建.txt → TEST/area_id/16.福建.txt

0
area_id/17.湖北.txt → TEST/area_id/17.湖北.txt

0
area_id/18.湖南.txt → TEST/area_id/18.湖南.txt

0
area_id/19.广东.txt → TEST/area_id/19.广东.txt

0
area_id/2.上海.txt → TEST/area_id/2.上海.txt

0
area_id/20.广西.txt → TEST/area_id/20.广西.txt

0
area_id/21.江西.txt → TEST/area_id/21.江西.txt

0
area_id/22.四川.txt → TEST/area_id/22.四川.txt

0
area_id/23.海南.txt → TEST/area_id/23.海南.txt

0
area_id/24.贵州.txt → TEST/area_id/24.贵州.txt

0
area_id/25.云南.txt → TEST/area_id/25.云南.txt

0
area_id/26.西藏.txt → TEST/area_id/26.西藏.txt

0
area_id/27.陕西.txt → TEST/area_id/27.陕西.txt

0
area_id/28.甘肃.txt → TEST/area_id/28.甘肃.txt

0
area_id/29.青海.txt → TEST/area_id/29.青海.txt

0
area_id/3.天津.txt → TEST/area_id/3.天津.txt

0
area_id/30.宁夏.txt → TEST/area_id/30.宁夏.txt

0
area_id/31.新疆.txt → TEST/area_id/31.新疆.txt

0
area_id/32.台湾.txt → TEST/area_id/32.台湾.txt

0
area_id/4.重庆.txt → TEST/area_id/4.重庆.txt

0
area_id/5.河北.txt → TEST/area_id/5.河北.txt

0
area_id/52993.港澳.txt → TEST/area_id/52993.港澳.txt

0
area_id/53283.海外.txt → TEST/area_id/53283.海外.txt

0
area_id/6.山西.txt → TEST/area_id/6.山西.txt

0
area_id/7.河南.txt → TEST/area_id/7.河南.txt

0
area_id/8.辽宁.txt → TEST/area_id/8.辽宁.txt

0
area_id/84.钓鱼岛.txt → TEST/area_id/84.钓鱼岛.txt

0
area_id/9.吉林.txt → TEST/area_id/9.吉林.txt

0
area_id/README.md → TEST/area_id/README.md

0
area_id/get_area_id.py → TEST/area_id/get_area_id.py

0
get_eid_fp.html → TEST/get_eid_fp.html

48
config.ini

@ -1,48 +0,0 @@ @@ -1,48 +0,0 @@
[config]
mode =
# eid, fp参数必须填写,具体请参考Wiki-常见问题 https://github.com/tychxn/jd-assistant/wiki/1.-%E4%BA%AC%E4%B8%9C%E6%8A%A2%E8%B4%AD%E5%8A%A9%E6%89%8B%E7%94%A8%E6%B3%95
eid = ""
fp = ""
# area到area_id目录下人工查找
area =
# 商品id
# 已经是XSS的sku_id了,用XSX换成100011513445
sku_id = 100021890778
# sku_id = 7816155
# XSX-100011513445 XSS-100021890778 百事可乐-7816155
# 设定时间 # 2020-12-09 10:00:00.100000
# 修改成每天的几点几分几秒几毫秒
buy_time = 2021-07-11 09:59:59.500
# 提交订单失败重试次数
retry = 10
# 查询库存请求超时(秒),可选配置,默认10秒
timeout = 10
# 默认UA
DEFAULT_USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36"
# 是否使用随机 useragent,默认为 false
random_useragent = false
[settings]
# 购买数量
buy_amount = 1
# 多进程数量
work_count = 1
[account]
# 支付密码
# 如果你的账户中有可用的京券(注意不是东券)或 在上次购买订单中使用了京豆,
# 那么京东可能会在下单时自动选择京券支付 或 自动勾选京豆支付。
# 此时下单会要求输入六位数字的支付密码。请在下方配置你的支付密码,如 123456 。
# 如果没有上述情况,下方请留空。
payment_pwd = ""
[messenger]
# 使用了Mirai_QQ的推送服务
enable = False
# mode改成group发送到QQ群
mode = user
# mode-user模式下填写QQ号
user_id = NULL
# mode-group下填写群号
group_id = NULL
server_address = NULL

23
logger.py

@ -1,23 +0,0 @@ @@ -1,23 +0,0 @@
import logging
import logging.handlers
'''
日志模块
'''
LOG_FILENAME = 'JD_SHOPPER.log'
logger = logging.getLogger()
def set_logger():
logger.setLevel(logging.INFO)
fmt = '[%(asctime)s] (%(module)s.%(funcName)s): <%(levelname)s> %(message)s'
datefmt = "%Y-%m-%d %H:%M:%S"
formatter = logging.Formatter(fmt=fmt, datefmt=datefmt)
console_handler = logging.StreamHandler()
console_handler.setFormatter(formatter)
logger.addHandler(console_handler)
file_handler = logging.handlers.RotatingFileHandler(
LOG_FILENAME, maxBytes=10485760, backupCount=5, encoding="utf-8")
file_handler.setFormatter(formatter)
logger.addHandler(file_handler)
set_logger()

17
main.py

@ -1,7 +1,6 @@ @@ -1,7 +1,6 @@
import sys
from config import global_config
from Seckill import Seckiller
from WaitingAndBuy import Waiter
from Config.settings import config
from Core.spider import Waiter
if __name__ == '__main__':
choiceList = """
@ -11,18 +10,12 @@ if __name__ == '__main__': @@ -11,18 +10,12 @@ if __name__ == '__main__':
功能列表
1.自动加购物车缺货等待上架自动下单
2.自动定时加购物车下单普通商品非秒杀抢购
"""
"""
print(choiceList)
choice_function = global_config.getRaw("config", "mode")
choice_function = config.global_config.getRaw("config", "mode")
if choice_function == '':
choice_function = input('请选择:')
global_config.setMode(choice_function)
# if choice_function == '1':
# jd_seckill = Seckiller()
# jd_seckill.reserve()
# elif choice_function == '2':
# jd_seckill = Seckiller()
# jd_seckill.seckill_by_proc_pool()
config.global_config.setMode(choice_function)
if choice_function == '1':
waiter = Waiter()
waiter.waitForSell()

77
message.py

@ -1,77 +0,0 @@ @@ -1,77 +0,0 @@
import json
import requests
from logger import logger
from config import global_config
def sendMessage(message):
enabled = global_config.getRaw("messenger", "enable")
if not enabled:
return
mode = global_config.getRaw("messenger", "mode")
if mode == "group":
groupid = global_config.getRaw("messenger", "group_id")
sendGroupMessage(message, groupid)
else:
userid = global_config.getRaw("messenger", "user_id")
sendFriendMessage(message, userid)
def sendFriendMessage(message, userid):
URL = global_config.getRaw("messenger", "server_address")
path = "sendFriendMessage"
try:
URL = "http://{}/{}".format(URL, path)
body = {
"sessionKey": "YourSession",
"target": userid,
"messageChain": [
{"type": "Plain", "text": message}
]
}
# sender = requests.post(URL, params = params, data=json.dumps(body))
sender = requests.post(URL, data=json.dumps(body))
mes = message.replace("\n", " ")
if len(mes) > 10:
mes = mes[:10] + "···"
logger.info("Send {}".format(mes))
# print(sender.request.url)
# sender.raise_for_status()
# print(sender.text)
return True
except:
logger.error("Message Send Failed")
return False
def sendGroupMessage(message, groupid):
URL = global_config.getRaw("messenger", "server_address")
path = "sendGroupMessage"
try:
URL = "http://{}/{}".format(URL, path)
body = {
"sessionKey": "YourSession",
"target": groupid,
"messageChain": [
{"type": "Plain", "text": message}
]
}
# sender = requests.post(URL, params = params, data=json.dumps(body))
sender = requests.post(URL, data=json.dumps(body))
mes = message.replace("\n", " ")
if len(mes) > 10:
mes = mes[:10] + "···"
logger.info("Send {}".format(mes))
# print(sender.request.url)
# sender.raise_for_status()
# print(sender.text)
return True
except:
logger.error("Message Send Failed")
return False

42
runserver.py

@ -0,0 +1,42 @@ @@ -0,0 +1,42 @@
from Core.core import main
from Logger.logger import logger
from Scheduler.scheduler import Timer
from Config.settings import config
from Server.server import server
from threading import Thread
from concurrent.futures import ProcessPoolExecutor
def running():
PROCESS_MODEL = config.settings("Server", "PROCESS_MODEL")
SCHEDULER = config.settings("Scheduler", "START_USING")
SERVER = config.settings("Server", "START_USING")
if not SCHEDULER:
thread_main = Thread(target=main)
thread_main.start()
else: # 调度器开启后main函数将被scheduler调度器代理,开启定时执行main
startTime = config.settings("Scheduler", "START_TIME")
skipWeekend = config.settings("Scheduler", "SKIP_WEEKEND")
scheduler = Timer(task=main, startTime=startTime, skipWeekend=skipWeekend)
thread_scheduler = Thread(target=scheduler.schedule)
thread_scheduler.start()
if SERVER:
if PROCESS_MODEL:
work_count = config.settings("Server", "PROCESS_COUNT")
server_process(work_count)
else:
thread_server = Thread(target=server)
thread_server.start()
def server_process(work_count=4):
with ProcessPoolExecutor(work_count) as pool:
for i in range(work_count):
pool.submit(server())
if __name__ == "__main__":
DEBUG = config.settings("Debug", "DEBUG")
if DEBUG:
logger.info("\n===== DEBUG MODE =====")
main()
else:
running()
Loading…
Cancel
Save