AutoUU--有品租赁自动上架的开源轻量化工具--程序篇、(二)

2024-02-08 04:50

本文主要是介绍AutoUU--有品租赁自动上架的开源轻量化工具--程序篇、(二),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

接前文的API篇。

如果你只是想使用,可直接跳到(三)实战篇

AutoUU--悠悠有品租赁自动上架的开源轻量化工具--实战篇、(三)_worldofgoo9的博客-CSDN博客

二、程序篇

1、配置文件结构

(暂时的)以一个configIndex.json文件作为顶层配置,在其中规定了其他配置文件的路径。总体结构如下:

<1>configIndex.json文件

configIndex.json格式如下:

其中configPath是一个列表,每一个对应了一个配置文件,之所以这里采用列表是为了控制多个配置文件的使能,使得配置更加灵活,例如:如果你有的时间只想上架一部分饰品(往往有这种需求的话会提前分类到不同的配置里),可以把不想上架的饰品对应的配置json去掉。

其他的字段为:

    "userName" : "你的用户名(手机)",

    "userPwd" : "登录密码",

    "retryTimes" : 3, 代表了当出现错误时,重试的次数,不建议更改

    "retryInterval" : 300.0, 代表了当出现错误时,重试前等待的时间(秒),不建议更改

    "runTime" : "17:00" 代表了上架流程每天的运行时间,根据自己的需求更改

    "maxInvPage" : 2  程序只会检测你的前X页库存,也就是这里的最大检测库存页数设定。库存页数根据自己需要上架库存量而定,建议够用即可,不要太大,否则会很慢还可能出问题。一般来说,新获得的物品都是在前面的,所以到cd的物品也是在前面,加上平均下来每一天的物品不会太多,所以一般2-3页就很多很多了。

<2>example.json文件

具体的配置文件格式如下,这里以“example.json”文件为例:


该文件是一个字典的列表,每一个字典({})代表了一项配置,用逗号隔开(注意最后一项后面没有逗号),对应一个物品,其中各个字段含义如下:

    {

        "float":"0.01342727987876540", 代表了物品的磨损值,是标识一个物品的关键(请使用有品上的磨损值而不是某buff的,因为某buff的显示不全)

        "strategy": "long", 代表了物品的上架策略,共有四种,会在下面解释。

        "shortPrice":3.0, 物品的短租租金,注意在不同策略下含义不同。

        "longPrice":2.5,  物品的长租租金,注意在不同策略下含义不同。

        "valuePrice":14999.0, 物品的押金设置,售价会设置为同值

        "maxDay":22, 物品的最长租赁时间

        "message":"长租好价 001", 物品的上架描述

        "name":"M9刺刀(★) | 虎牙" ,物品的名称,也可以看做备忘录,可以填写任意字符,不会影响上架流程,但是如果你填写了之后以后就知道这个是什么物品,不会忘了。

    },

这里具体解释一下"strategy"字段,一共有四种设置:

"short": 顾名思义,代表了优先短租,短租价格会设置为(市场短租底价*0.97-0.01),长租价格会设置为(市场长租底价*1.015+0.01)

"long": 代表了优先短租,短租价格会设置为(市场短租底价*1.015+0.01),长租价格会设置为(市场长租底价*0.97-0.01)

"auto": 代表了无偏向性的自动定价,长短租价格会分别设置为(底价租金*0.985)。

"fix": 代表了固定的价格设置,长短租价格会固定设置为"shortPrice"和"longPrice"中的值。

此外,在自动定价的模式下(short/long/auto),"shortPrice"和"longPrice"代表了定价的下限,也就是说如果自动定价获取的价格小于你设置的下限,那么价格就会被设置为该下限值。

2、程序设计

<1>程序执行框架

程序执行过程的框架如下图:

<2>读取配置信息

读取配置信息函数接收地址路径列表参数pathList,调用loadConfigs会依次读取参数地址路径列表中的所有配置文件并添加进当前配置中。

    def loadConfigs(self, pathList, encoding = "utf-8"):

        for path in pathList:

            self.__loadConfig__(path)

    def __loadConfig__(self, path="cfg.json", encoding="utf-8"):

        with open(path, "r", encoding=encoding) as f:

            config = json.load(f)

        num = len(config)

        result = {}

        for item in config:

            result[str(item['float'])] = item

        self.printLog(f"Successfully Load {num} Configurations.")

        self.config.update(result)

        return result

<3>登录、获取用户信息

登录函数接收用户名密码作为参数,获取会话的Token以及用户id,昵称并保存。

    def login(self, userName, userPwd):

        url = "https://api.youpin898.com/api/user/Auth/PwdSignIn"

        headers = {

            "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.64 Safari/537.36",

            "accept": "application/json, text/plain, */*",

            "accept-encoding": "gzip, deflate, br",

            "accept-language": "zh-CN,zh;q=0.9",

            "apptype": '1',

            "origin": "https://www.youpin898.com",

            "referer": "https://www.youpin898.com/",

            "sec-ch-ua": '" Not A;Brand";v="99", "Chromium";v="101", "Google Chrome";v="101"',

            "sec-ch=-ua=mobile": "?0",

            "sec-ch-ua-platform": '"Windows"',

            "sec-fetch-dest": "empty",

            "sec-fetch-mode": "cors",

            "sec-fetch-site": "same-site"

        }

        data = {

            "code": "",

            "SessionId": "",

            "UserName": userName,

            "UserPwd": userPwd

        }

        res = requests.post(url, headers=headers, json=data).json()

        if(res["Code"] == 0):

            token = res['Data']['Token']

            self.printLog(f"Get Session Token:{token}")

            self.token = token

            self.isLogin = True

        else:

            raise MyError(f"Get Session Failed. Res code:{res['Code']},res:{res}")

        url = "https://api.youpin898.com/api/user/Account/GetUserInfo"

        headers = {

            "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.64 Safari/537.36",

            "accept": "application/json, text/plain, */*",

            "accept-encoding": "gzip, deflate, br",

            "accept-language": "zh-CN,zh;q=0.9",

            "apptype": '1',

            "authorization": f"Bearer {token}",

            "origin": "https://www.youpin898.com",

            "referer": "https://www.youpin898.com/",

            "sec-ch-ua": '" Not A;Brand";v="99", "Chromium";v="101", "Google Chrome";v="101"',

            "sec-ch=-ua=mobile": "?0",

            "sec-ch-ua-platform": '"Windows"',

            "sec-fetch-dest": "empty",

            "sec-fetch-mode": "cors",

            "sec-fetch-site": "same-site"

        }

        res = requests.get(url, headers=headers).json()

        if(res["Code"] == 0):

            self.userId = res['Data']['UserId']

            self.name = res['Data']['NickName']

            #self.printLog(

            #    f"Get User Info. Id:{res['Data']['UserId']},Name:{res['Data']['NickName']}")

            self.isLogin = True

            #return True

        else:

            raise MyError(f"Get User Info Failed. Response code:{res['Code']}, body:{res}")

最主要的步骤为发送两个请求,分别为登录获取Token,在获取Token后请求获得用户信息,用来获得用户的Id,之后会用到。

<4>获取库存信息

获取库存信息不需要额外的参数,会根据设置的最大页数来获取所要操作的库存信息

    def getInv(self):

        if(not self.isLogin):

            raise MyError("Please Login first.")

        headers = {

            "app-version": "4.1.3",

            "version": "4.1.3",

            "platform": "ios",

            "accept": "*/*",

            "accept-encoding": "br;q=1.0, gzip;q=0.9, deflate;q=0.8",

            "accept-language": "zh-Hans-CN;q=1.0, en-CN;q=0.9",

            "AppType": '3',

            "authorization": f"Bearer {self.token}",

        } # 这里实际采用的是IOS客户端的API,因为网页端获取库存有时会崩溃

        self.invData = []

        pageIndex = 1

        while(pageIndex <= self.maxInvPageIndex):

            url = f'''https://api.youpin898.com/api/commodity/Inventory/GetUserInventoryDataList?AppType=3&GameID=730&IsRefresh=false&ItemStatus=0&PageIndex={pageIndex}&PageSize=30&Platform=ios&Sort=0&Version=4.1.3'''

            res = requests.get(url, headers=headers).json()

            pageIndex += 1

            if(res["Code"] == 0):

                self.invData += res['Data']['ItemsInfos']

                self.operateSleep()

            else:

                raise MyError(f"Get Inv Info Failed. Response code:{res['Code']}, body:{res}")

        self.printLog(f"Get Inv Info Success. Total Num : {len(self.invData)}")

<5>获取市场价格

获取市场价格是一个私有函数,参数为物品的ID,会返回当前市场的短长租底价。

    def __getMarketPrice__(self, itemId):

        if(not self.isLogin):

            raise MyError("Please Login first.")      

        url = "https://api.youpin898.com/api/homepage/es/commodity/GetCsGoPagedList"

        itemId = str(itemId)

        headers = {

            "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.64 Safari/537.36",

            "accept": "application/json, text/plain, */*",

            "accept-encoding": "gzip, deflate, br",

            "accept-language": "zh-CN,zh;q=0.9",

            "apptype": '1',

            "authorization": f"Bearer {self.token}",

            'content-type': "application/json;charset=UTF-8",

            "origin": "https://www.youpin898.com",

            "referer": "https://www.youpin898.com/",

            "sec-ch-ua": '" Not A;Brand";v="99", "Chromium";v="101", "Google Chrome";v="101"',

            "sec-ch=-ua=mobile": "?0",

            "sec-ch-ua-platform": '"Windows"',

            "sec-fetch-dest": "empty",

            "sec-fetch-mode": "cors",

            "sec-fetch-site": "same-site"

            # "Connection": "keep-alive",

        }

        queryShort = {"templateId": itemId, "pageSize": 30, "pageIndex": 1,

                      "sortType": 1, "listSortType": 2, "listType": 30, "stickersIsSort": False}

        queryLong = {"templateId": itemId, "pageSize": 30, "pageIndex": 1,

                     "sortType": 1, "listSortType": 3, "listType": 30, "stickersIsSort": False}

        res = requests.post(url, headers=headers, json=queryShort).json()

        if(res["Code"] == 0):

            shortPrice = float(res["Data"]['CommodityList'][0]['LeaseUnitPrice'])

        else:

            raise MyError(f"Get Short-Lease Price Failed. Response code:{res['Code']}, body:{res}")

        res = requests.post(url, headers=headers, json=queryLong).json()

        if(res["Code"] == 0):

            longPrice = float(res["Data"]['CommodityList'][0]['LeaseUnitPrice'])

        else:

            raise MyError(f"Get Long-Lease Price Failed. Response code:{res['Code']}, body:{res}")

        return {

            "shortPrice": shortPrice,

            "longPrice": longPrice,

        }

        # Query data sort by short / long lease price.

这里参数itemId对应了该物品在uu中的ID号,在获取库存信息时会得到。

<6>定价

定价函数不需要额外参数,会根据前面获取到的库存数据逐一进行判断是否要上架以及定价多少。

    def doPricing(self):

        if(not self.isLogin):

            raise MyError("Please Login first.")

        invNum = len(self.invData)

        price = [{} for i in range(invNum)] # Responding to each item in INV

        #itemTemp = {}

        for i in range(invNum):

            if(self.invData[i]['AssetInfo'] is None):

                price[i] = {"flag": False}

                continue

            itemFloat = str(self.invData[i]['AssetInfo']['Abrade'])

            assetId = self.invData[i]['SteamAssetID']

            itemId = self.invData[i]['TemplateInfo']['Id']

            if((itemFloat not in self.config.keys()) or (self.invData[i]['Tradable'] == False) ): #or (itemId in itemTemp.keys())

                price[i] = {"flag": False}

                continue

            #print(f"{i} {itemFloat} {self.invData[i]['OnSale']} {self.invData[i]}")

            time.sleep(self.timeSleep)

            marketPrice = self.__getMarketPrice__(itemId)

            shortMarketPrice = marketPrice['shortPrice']

            longMarketPrice = marketPrice['longPrice']

            # Do pricing.

            strategy = self.config[itemFloat]['strategy']

            if(strategy == "auto"):

                shortPrice = max(

                    self.config[itemFloat]['shortPrice'], shortMarketPrice*0.985-0.01)

                longPrice = max(

                    self.config[itemFloat]['longPrice'], longMarketPrice*0.985-0.01)

            elif(strategy == "short"):

                shortPrice = max(

                    self.config[itemFloat]['shortPrice'], shortMarketPrice*0.97-0.01)

                longPrice = max(

                    self.config[itemFloat]['longPrice'], longMarketPrice*1.015+0.01)

            elif(strategy == "long"):

                shortPrice = max(

                    self.config[itemFloat]['shortPrice'], shortMarketPrice*1.015+0.01)

                longPrice = max(

                    self.config[itemFloat]['longPrice'], longMarketPrice*0.97-0.01)

            elif(strategy == "fix"):

                # Fixed.

                shortPrice = self.config[itemFloat]['shortPrice']

                longPrice = self.config[itemFloat]['longPrice']

            else:

               

                self.printLog(

                    f"Unvalid strategy {strategy} ------ item float {itemFloat}", 2)

                price[i] = {"flag": False}

                continue

            shortPrice = round(shortPrice, 2)

            longPrice = round(longPrice, 2)

            valuePrice = self.config[itemFloat]['valuePrice']

            maxDay = max(self.config[itemFloat]['maxDay'], 8)

            message = self.config[itemFloat]['message']

            #itemTemp[itemId] = True

            price[i] = {"flag": True, "assetId": int(assetId), "templateId": int(

                itemId), "itemFloat": itemFloat, "shortPrice": shortPrice, "longPrice": longPrice, "valuePrice": valuePrice, "maxDay": maxDay, "message": message}

        self.price = price

<7>上架

上架函数不需要额外参数,其信息来源于上面的定价函数的执行结果,其具体执行过程为执行两个请求,包括获取AppKey以及上架操作,对应了API篇的分析结果。

    def putOnSale(self):

        if(not self.isLogin):

            raise MyError("Please Login first.")

       

        itemInfos = []

        for item in self.price:

            if(item['flag'] == True):

                itemInfos.append(

                    {

                        "assetId": item['assetId'], "commodityTemplateId": item['templateId'], "remark": item['message'], "charge": item['valuePrice'], "price": item['valuePrice'],

                        "IsCanLease": True, "IsCanSold": True,

                        "LeaseMaxDays": item['maxDay'], "LeaseUnitPrice": item['shortPrice'], "LongLeaseUnitPrice": item['longPrice'], "LongLeaseDays": item['maxDay'], "LeaseDeposit": item['valuePrice']

                    }

                )

        num = len(itemInfos)

        if(num == 0):

            self.printLog(f"Nothing to be put onto sell.")

            return True

        # Apply App for Key

        url = f"https://api.youpin898.com/api/youpin/detect/detection/1/{self.userId}/0/app"

        #itemId = str(itemId)

        headers = {

            # "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:72.0) Gecko/20100101 Firefox/72.0 micromessenger",

            "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.64 Safari/537.36",

            "accept": "application/json, text/plain, */*",

            "accept-encoding": "gzip, deflate, br",

            "accept-language": "zh-CN,zh;q=0.9",

            "apptype": '1',

            "authorization": f"Bearer {self.token}",

            'content-type': "application/json;charset=UTF-8",

            "origin": "https://www.youpin898.com",

            "referer": "https://www.youpin898.com/",

            "sec-ch-ua": '" Not A;Brand";v="99", "Chromium";v="101", "Google Chrome";v="101"',

            "sec-ch=-ua=mobile": "?0",

            "sec-ch-ua-platform": '"Windows"',

            "sec-fetch-dest": "empty",

            "sec-fetch-mode": "cors",

            "sec-fetch-site": "same-site"

            # "Connection": "keep-alive",

        }

        res = requests.get(url, headers=headers).json()

        if(res["code"] == 0):

            key = res['data']['key']

            self.printLog(f"Get App Key Succ. Key:{key}")

        else:

            raise MyError(f"Get App Key Failed. Response code:{res['Code']}, body:{res}")

            #return False

        # Sell items

        url = f"https://api.youpin898.com/api/commodity/Inventory/SellInventoryWithLease"

        payload = {

            "gameId": "730",  # Csgo

            "itemInfos": itemInfos,

            "key": key

        }

        res = requests.post(url, headers=headers, json=payload).json()

        if(res["Code"] == 0):

            self.printLog(f"Sell {num} items Succ.")

            return num

        else:

            raise MyError(f"Put on Sale Failed. Response code:{res['Code']}, body:{res}")

<8>高层执行代码

其中uu.operateSleep()仅起到暂停作用,用于降低服务器瞬时压力,默认为暂停3s

    def operateSleep(self):

        time.sleep(self.timeSleep)

def run():

    # Try for muiltiple times

    configIndex = loadConfigIndex()

    uu = AutoUU(maxInvPageIndex = configIndex['maxInvPage'])

    uu.printLog("Start !")

    retryTimes = configIndex["retryTimes"]

    retryInterval = configIndex["retryInterval"]

    userName = configIndex["userName"]

    userPwd = configIndex["userPwd"]

    flag = 0 # tried times

    while(flag < retryTimes and flag >= 0):

        if(flag > 0):

            uu.printLog("Retrying , time : " + str(flag) , 1)

        flag += 1

        uu.loadConfigs(pathList = configIndex["configPath"])

        try:

            uu.login(userName, userPwd)

            uu.printLog("Login Finished !")

            uu.operateSleep()

            uu.getInv()

            uu.printLog("Get INV Finished !")

            uu.operateSleep()

            uu.doPricing()

            uu.printLog("Do Pricing Finished !")

            uu.operateSleep()

            uu.putOnSale()

            uu.printLog("Put On Sale Finished !")

            uu.operateSleep()

           

        except MyError as e:

            uu.printLog(e.value , 1)

            time.sleep(retryInterval)

       

        except Exception as e:

            uu.printLog(e , 2)

            time.sleep(retryInterval)

       

        else:

            flag = -1 # Finish

            uu.printLog(f"Routine Finished !")

            break

这篇关于AutoUU--有品租赁自动上架的开源轻量化工具--程序篇、(二)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/689948

相关文章

Python脚本实现自动删除C盘临时文件夹

《Python脚本实现自动删除C盘临时文件夹》在日常使用电脑的过程中,临时文件夹往往会积累大量的无用数据,占用宝贵的磁盘空间,下面我们就来看看Python如何通过脚本实现自动删除C盘临时文件夹吧... 目录一、准备工作二、python脚本编写三、脚本解析四、运行脚本五、案例演示六、注意事项七、总结在日常使用

java图像识别工具类(ImageRecognitionUtils)使用实例详解

《java图像识别工具类(ImageRecognitionUtils)使用实例详解》:本文主要介绍如何在Java中使用OpenCV进行图像识别,包括图像加载、预处理、分类、人脸检测和特征提取等步骤... 目录前言1. 图像识别的背景与作用2. 设计目标3. 项目依赖4. 设计与实现 ImageRecogni

基于Python开发电脑定时关机工具

《基于Python开发电脑定时关机工具》这篇文章主要为大家详细介绍了如何基于Python开发一个电脑定时关机工具,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录1. 简介2. 运行效果3. 相关源码1. 简介这个程序就像一个“忠实的管家”,帮你按时关掉电脑,而且全程不需要你多做

基于C#实现PDF文件合并工具

《基于C#实现PDF文件合并工具》这篇文章主要为大家详细介绍了如何基于C#实现一个简单的PDF文件合并工具,文中的示例代码简洁易懂,有需要的小伙伴可以跟随小编一起学习一下... 界面主要用于发票PDF文件的合并。经常出差要报销的很有用。代码using System;using System.Col

redis-cli命令行工具的使用小结

《redis-cli命令行工具的使用小结》redis-cli是Redis的命令行客户端,支持多种参数用于连接、操作和管理Redis数据库,本文给大家介绍redis-cli命令行工具的使用小结,感兴趣的... 目录基本连接参数基本连接方式连接远程服务器带密码连接操作与格式参数-r参数重复执行命令-i参数指定命

SpringBoot项目启动后自动加载系统配置的多种实现方式

《SpringBoot项目启动后自动加载系统配置的多种实现方式》:本文主要介绍SpringBoot项目启动后自动加载系统配置的多种实现方式,并通过代码示例讲解的非常详细,对大家的学习或工作有一定的... 目录1. 使用 CommandLineRunner实现方式:2. 使用 ApplicationRunne

Springboot的ThreadPoolTaskScheduler线程池轻松搞定15分钟不操作自动取消订单

《Springboot的ThreadPoolTaskScheduler线程池轻松搞定15分钟不操作自动取消订单》:本文主要介绍Springboot的ThreadPoolTaskScheduler线... 目录ThreadPoolTaskScheduler线程池实现15分钟不操作自动取消订单概要1,创建订单后

Python pyinstaller实现图形化打包工具

《Pythonpyinstaller实现图形化打包工具》:本文主要介绍一个使用PythonPYQT5制作的关于pyinstaller打包工具,代替传统的cmd黑窗口模式打包页面,实现更快捷方便的... 目录1.简介2.运行效果3.相关源码1.简介一个使用python PYQT5制作的关于pyinstall

python实现自动登录12306自动抢票功能

《python实现自动登录12306自动抢票功能》随着互联网技术的发展,越来越多的人选择通过网络平台购票,特别是在中国,12306作为官方火车票预订平台,承担了巨大的访问量,对于热门线路或者节假日出行... 目录一、遇到的问题?二、改进三、进阶–展望总结一、遇到的问题?1.url-正确的表头:就是首先ur

Spring使用@Retryable实现自动重试机制

《Spring使用@Retryable实现自动重试机制》在微服务架构中,服务之间的调用可能会因为一些暂时性的错误而失败,例如网络波动、数据库连接超时或第三方服务不可用等,在本文中,我们将介绍如何在Sp... 目录引言1. 什么是 @Retryable?2. 如何在 Spring 中使用 @Retryable