首页
影视
壁纸
留言
关于
今日热榜
推荐
音乐解锁
听歌
阅读器
听歌2
小乞丐
摸鱼
Search
1
Navicat Premium for Mac 中文破解版 (强大的数据库管理工具)
9,628 阅读
2
植物大战僵尸中文版 for Mac (兼容 M1系统) 中文版
5,767 阅读
3
CleanMyMac X 4.15.2 中文破解版 (Mac优化清理工具)
3,295 阅读
4
PDF Expert 中文破解版 (好用的PDF编辑器)
3,044 阅读
5
Parallels Desktop 17 中文版 (PD虚拟机无限试用版本)
2,818 阅读
生活杂记
macOS
编程技术
奇技淫巧
音乐
视频
δ
Search
标签搜索
python
漫画
php
mac
redis
mysql
mac 软件
macOS
音乐
极客爱情
数据库
游戏
吊打面试官
2.0
面试
罗小黑
linux
纯音乐
使用教程
B站
Kain
累计撰写
210
篇文章
累计收到
26
条评论
首页
栏目
生活杂记
macOS
编程技术
奇技淫巧
音乐
视频
δ
页面
影视
壁纸
留言
关于
今日热榜
推荐
音乐解锁
听歌
阅读器
听歌2
小乞丐
摸鱼
搜索到
26
篇与
的结果
2021-02-06
支付宝五福53张自动领取程序
支付宝联合其他52个产品推出送五福的活动,用pyhton简单写了一个自动连续领取的程序,只需要每隔60s把手机的验证码输入即可。
2021年02月06日
66 阅读
0 评论
0 点赞
2021-02-05
终于搞懂了Python模块之间的相互引用问题
摘要:详细讲解了相对路径和绝对路径的引用方法。在某次运行过程中出现了如下两个报错:报错1: ModuleNotFoundError: No module named '__main__.src_test1'; '__main__' is not a package 报错2: ImportError: attempted relative import with no known parent package 于是基于这两个报错探究了一下python3中的模块相互引用的问题,下面来逐个解析,请耐心看完。好的,我们先来构造第一个错,测试代码结构如下:|--- test_main.py |--- src |--- __init__.py |--- src_test1.py |--- src_test2.pysrc_test2.py 代码class Test2(object): def foo(self): print('I am foo')src_test1.py 代码,引用Test2模块from .src_test2 import Test2 def fun1(): t2 = Test2() t2.foo() if __name__ == "__main__": fun1()此时运行 src_test1.py 报错“No module named '__main__.src_test1'; '__main__' is not a package”问题原因:主要在于引用src_test2模块的时候,用的是相对路径".",在import语法中翻译成"./",也就是当前目录下,按这样理解也没有问题,那为什么报错呢?从 PEP 328 中,我们找到了关于 the relative imports(相对引用)的介绍通俗一点意思就是,你程序入口运行的那个模块,就默认为主模块,他的name就是‘main’,然后会将本模块import中的点(.)替换成‘__main__’,那么 .src_test2就变成了 __main__.src_test2,所以当然找不到这个模块了。解决方法:因此,建议的做法是在 src同层级目录创建 引用模块 test_main.py(为什么不在src目录下创建,待会下一个报错再讲),并引用src_test1模块,代码如下:from src.src_test1 import fun1 if __name__ == "__main__": fun1()那为什么这样执行就可以了呢,其中原理是什么呢?我是这样理解的(欢迎纠正):test_main执行时,他被当做根目录,因此他引用的src.src_test1 是绝对路径,这样引用到哪都不会错,此时他的name=‘main’,当执行src_test1的时候,注意了此时test1的name是 src.src_test1,那么在test1中使用的是相对路径,查找逻辑是先找到父节点(src目录),再找父节点下面的src_test2,因此可以成功找到,Bingo!辅证:构造一个例子,就可以理解上面的 执行目录就是根目录 的说法了,修改test1,使引用test_main:from .. import test_main 报错:ValueError: attempted relative import beyond top-level packageOK,那继续构造第二个报错:上文中说过,解决main 的问题,就是创建一个模块,来调用使用相对路径的模块,那么为什么我不能在相同目录下创建这个文件来调用呢?让我们来测试下代码:创建test_src.py文件,代码结构变更如下:|--- test_main.py |--- src |--- __init__.py |--- src_test1.py |--- src_test2.pys |--- test_src.pytest_src 代码:from src_test1 import fun1 if __name__ == "__main__": fun1()执行报错:ImportError: attempted relative import with no known parent package问题原因:当执行test_src时,按上文理解,此时执行文件所在的目录为根目录,那么引用test1的时候,需要注意的是,此时test1的name属性不再是src.src_test1,因为程序感知不到src的存在,此时他的绝对路径是 src_test1,此时再次引用相对路径查找的test2,同样的步骤,需要先找到父节点,而此时他自己就是根节点了,已经没有父节点了,因此报错“no known parent package”。解决方法:此时为了避免父节点产生矛盾,因此将test1中的引入去掉相对引用即可from .src_test2 import Test2 --> from src_test2 import Test2继续深入:那使用相对路径和绝对路径,编译器是怎么找到这个模块的呢?执行import的时候,存在一个引入的顺序,即优先查找执行目录下有没有此文件,如没有,再查找lib库下,如还没有,再查找sys.path中的路径,如再没有,报错。所以不管是当前目录,还是 sys.path中的目录,都可以查到 src_test2这个模块,就可以编译成功。号外:解决完上述问题后,不管我们用哪种方式,我们调试代码时,都是单个文件调试,但此时根目录就不对了,import方式又要改动,执行起来很麻烦,所以这里推荐另一种方式(有更好的方式欢迎留言),使用sys.path.append()的方法import sys,os sys.path.append(os.getcwd()) from src.src_test2 import Test2使用append的方式,将程序文件根目录放进了sys.path中,然后再引用绝对路径,这样的方式,不管使用上文中的第一或第二执行方式都可以调用,也可以单独编译test1文件,不用修改import路径,也是相对安全的方式。但是缺点就是,如果你修改了某一个包名,需要将所有引用地方都修改一下,工作量大,所以因地制宜。综上,详细讲解了相对路径和绝对路径的引用方法,现在你应该对import导入的问题有了清晰的理解吧备注:本文基于Python3.7版本测试
2021年02月05日
143 阅读
0 评论
0 点赞
2020-12-22
按日落时间执行小米刷步
按日落时间执行小米刷步
2020年12月22日
146 阅读
0 评论
0 点赞
2020-10-10
【Python]】基于Python3实现的m3u8批量下载器 解密&合并&多线程 (开车新姿势~)
一、前言闲来无聊写的m3u8批量下载器,实现了多线程下载、AES常规解密、合并、批量下载四大功能。二、m3u8概述M3U8 是 Unicode 版本的 M3U,用 UTF-8 编码。"M3U" 和 "M3U8" 文件都是苹果公司使用的 HTTP Live Streaming(HLS) 协议格式的基础,HLS 的工作原理是把整个流分成一个个小的基于 HTTP 的文件来下载,每次只下载一些。当媒体流正在播放时,客户端可以选择从许多不同的备用源中以不同的速率下载同样的资源,允许流媒体会话适应不同的网络速率,所以广泛用于在线视频的播放、传输。一个m3u8文件主要由信息头(记录版本、是否加密、key的位置)、ts流列表两个部分构成。下面是常见的两类m3u8文件。m3u8表现形式1(假设m3u8文件的url=https://www.xxx.com/yyy/zzz/index.m3u8)这种m3u8链接对应的视频可能有多种分辨率,比如下面这个例子只有720x480这个分辨率,对应的相对url为1000kb/hls/index.m3u8,是一个相对路径,720x480分辨率的视频绝对路径就是https://www.xxx.com/yyy/zzz/1000kb/hls/index.m3u8,需要再次访问这个链接下载这个分辨率对应的m3u8文件。#EXTM3U #EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=1000000,RESOLUTION=20x480 000kb/hls/index.m3u8m3u8表现形式2(假设m3u8文件的url=https://www.xxx.com/yyy/zzz/index.m3u8)这种m3u8已经把ts列表直接放进来了,所以下载所有的ts流组合即可得到视频的全部内容。注意第5行,注明了该视频进行了加密,加密方式为AES-128(默认是CBC模式),加密key的路径为key.key,是一个相对路径,套上基路径就是https://www.xxx.com/yyy/zzz/key.key,(注:key也可能这个key的url是一个http开头的绝对路径)。有的m3u8可能还在这一行加一个IV,也就是AES-CBC加密、解密中的IV。下面的例子中ts也是相对路径,同样需要加上基路径,比如第1个ts的绝对路径就是https://www.xxx.com/yyy/zzz/QxiMvI3688000.ts#EXTM3U #EXT-X-VERSION:3 #EXT-X-TARGETDURATION:1 #EXT-X-MEDIA-SEQUENCE:0 #EXT-X-KEY:METHOD=AES-128,URI="key.key" #EXTINF:0.834167, QxiMvI3688000.ts #EXTINF:0.834167, QxiMvI3688001.ts #EXTINF:0.834167, QxiMvI3688002.ts #EXTINF:0.834167, QxiMvI3688003.ts #EXTINF:0.834167, QxiMvI3688004.ts #EXTINF:0.834167, QxiMvI3688005.ts ...三、基于Python3实现的m3u8批量下载器(解密&多线程&合并)1、下载思路经过简单的分析m3u8协议及其文件格式,现在只要把他们串起来就好了。①、下载m3u8文件,如果其内容的表示形式是第1种,则还需要再次访问对应的分辨率的url,重新下载m3u8 ②、解析m3u8,判断是否加密了(需要提取加密方式、加密key、IV),提取ts列表 ③、多线程下载所有ts(注意别打乱顺序,在m3u8文件中的顺序就是在完整视频中的顺序,所以需要记录原来的顺序,或者按照顺序进行ts重命名) ④、合并(如果加密了,则对每个ts解密) ⑤、调用FFmpeg,将合并好的视频信息放入一个mp4容器中(直接放在mp4文件也行) ⑥、回到①,开始下载下一个m3u82、Python源码实现受博主编码能力影响,加上博主又很懒,怎么简单怎么来,代码冗余度比较高。。。下面的代码已经过了4-5千个m3u8的下载测试,但是不能保证没有bug,如有问题欢迎斧正哈~# UTF-8 # author hestyle # desc: 必须在终端直接执行,不能在pycharm等IDE中直接执行,否则看不到动态进度条效果 import os import sys import m3u8 import requests import traceback import threadpool import shutil from Crypto.Cipher import AES headers = { "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9", "Connection": "Keep-Alive", "Accept-Encoding": "gzip, deflate, br", "Accept-Language": "zh-CN,zh;q=0.9", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.102 Safari/537.36" } ######################配置信息########################## # m3u8链接批量输入文件 m3u8InputFilePath = "/Users/kain/Code/GIT/m3u8downloader/input/m3u8s_input.txt" # 视频保存路径 saveRootDirPath = "/Users/kain/Code/GIT/m3u8downloader/output" # 下载出错的m3u8保存文件 errorM3u8InfoDirPath = "/Users/kain/Code/GIT/m3u8downloader/output/error.txt" # m3u8文件、key文件下载尝试次数,ts流默认无限次尝试下载,直到成功 m3u8TryCountConf = 10 # 线程数(同时下载的分片数) processCountConf = 50 ####################################################### # 全局变量 # 全局线程池 taskThreadPool = None # 当前下载的m3u8 url m3u8Url = None # url前缀 rootUrlPath = None # title title = None # ts count sumCount = None # 已处理的ts doneCount = None # cache path cachePath = saveRootDirPath + "/cache" # log path logPath = cachePath + "/log.log" # log file logFile = None # 1、下载m3u8文件 def getM3u8Info(): global m3u8Url global logFile global rootUrlPath tryCount = m3u8TryCountConf while True: if tryCount < 0: print("\t{0}下载失败!".format(m3u8Url)) logFile.write("\t{0}下载失败!".format(m3u8Url)) return None tryCount = tryCount - 1 try: response = requests.get(m3u8Url, headers=headers, timeout=20, allow_redirects=True) if response.status_code == 301: nowM3u8Url = response.headers["location"] print("\t{0}重定向至{1}!".format(m3u8Url, nowM3u8Url)) logFile.write("\t{0}重定向至{1}!\n".format(m3u8Url, nowM3u8Url)) m3u8Url = nowM3u8Url continue expected_length = int(response.headers.get('Content-Length')) actual_length = len(response.content) if expected_length > actual_length: raise Exception("m3u8下载不完整") print("\t{0}下载成功!".format(m3u8Url)) logFile.write("\t{0}下载成功!".format(m3u8Url)) rootUrlPath = m3u8Url[0:m3u8Url.rindex('/')] break except TimeoutError: print("\t{0}下载失败!正在重试".format(m3u8Url)) logFile.write("\t{0}下载失败!正在重试".format(m3u8Url)) traceback.print_exc() # 解析m3u8中的内容 m3u8Info = m3u8.loads(response.text) # 有可能m3u8Url是一个多级码流 if m3u8Info.is_variant: print("\t{0}为多级码流!".format(m3u8Url)) logFile.write("\t{0}为多级码流!".format(m3u8Url)) for rowData in response.text.split('\n'): # 寻找响应内容的中的m3u8 if rowData.endswith(".m3u8"): m3u8Url = m3u8Url.replace("index.m3u8", rowData) rootUrlPath = m3u8Url[0:m3u8Url.rindex('/')] return getM3u8Info() # 遍历未找到就返回None print("\t{0}响应未寻找到m3u8!".format(response.text)) logFile.write("\t{0}响应未寻找到m3u8!".format(response.text)) return None else: return m3u8Info # 2、下载key文件 def getKey(keyUrl): global logFile tryCount = m3u8TryCountConf while True: if tryCount < 0: print("\t{0}下载失败!".format(keyUrl)) logFile.write("\t{0}下载失败!".format(keyUrl)) return None tryCount = tryCount - 1 try: response = requests.get(keyUrl, headers=headers, timeout=20, allow_redirects=True) if response.status_code == 301: nowKeyUrl = response.headers["location"] print("\t{0}重定向至{1}!".format(keyUrl, nowKeyUrl)) logFile.write("\t{0}重定向至{1}!\n".format(keyUrl, nowKeyUrl)) keyUrl = nowKeyUrl continue expected_length = int(response.headers.get('Content-Length')) actual_length = len(response.content) if expected_length > actual_length: raise Exception("key下载不完整") print("\t{0}下载成功!key = {1}".format(keyUrl, response.content.decode("utf-8"))) logFile.write("\t{0}下载成功! key = {1}".format(keyUrl, response.content.decode("utf-8"))) break except : print("\t{0}下载失败!".format(keyUrl)) logFile.write("\t{0}下载失败!".format(keyUrl)) return response.text # 3、多线程下载ts流 def mutliDownloadTs(playlist): global logFile global sumCount global doneCount global taskThreadPool taskList = [] # 每个ts单独作为一个task for index in range(len(playlist)): dict = {"playlist": playlist, "index": index} taskList.append((None, dict)) # 重新设置ts数量,已下载的ts数量 doneCount = 0 sumCount = len(taskList) printProcessBar(sumCount, doneCount, 50) # 构造thread pool requests = threadpool.makeRequests(downloadTs, taskList) [taskThreadPool.putRequest(req) for req in requests] # 等待所有任务处理完成 taskThreadPool.wait() print("") return True # 4、下载单个ts playlists[index] def downloadTs(playlist, index): global logFile global sumCount global doneCount global cachePath global rootUrlPath succeed = False while not succeed: # 文件名格式为 "00000001.ts",index不足8位补充0 outputPath = cachePath + "/" + "{0:0>8}.ts".format(index) outputFp = open(outputPath, "wb+") if playlist[index].startswith("http"): tsUrl = playlist[index] else: tsUrl = rootUrlPath + "/" + playlist[index] try: response = requests.get(tsUrl, timeout=10, headers=headers, stream=True) if response.status_code == 200: expected_length = int(response.headers.get('Content-Length')) actual_length = len(response.content) if expected_length > actual_length: raise Exception("分片下载不完整") outputFp.write(response.content) doneCount += 1 printProcessBar(sumCount, doneCount, 50) logFile.write("\t分片{0:0>8} url = {1} 下载成功!".format(index, tsUrl)) succeed = True except Exception as exception: logFile.write("\t分片{0:0>8} url = {1} 下载失败!正在重试...msg = {2}".format(index, tsUrl, exception)) outputFp.close() # 5、合并ts def mergeTs(tsFileDir, outputFilePath, cryptor, count): global logFile outputFp = open(outputFilePath, "wb+") for index in range(count): printProcessBar(count, index + 1, 50) logFile.write("\t{0}\n".format(index)) inputFilePath = tsFileDir + "/" + "{0:0>8}.ts".format(index) if not os.path.exists(outputFilePath): print("\n分片{0:0>8}.ts, 不存在,已跳过!".format(index)) logFile.write("分片{0:0>8}.ts, 不存在,已跳过!\n".format(index)) continue inputFp = open(inputFilePath, "rb") fileData = inputFp.read() try: if cryptor is None: outputFp.write(fileData) else: outputFp.write(cryptor.decrypt(fileData)) except Exception as exception: inputFp.close() outputFp.close() print(exception) return False inputFp.close() print("") outputFp.close() return True # 6、删除ts文件 def removeTsDir(tsFileDir): # 先清空文件夹 for root, dirs, files in os.walk(tsFileDir, topdown=False): for name in files: os.remove(os.path.join(root, name)) for name in dirs: os.rmdir(os.path.join(root, name)) os.rmdir(tsFileDir) return True # 7、convert to mp4(调用了FFmpeg,将合并好的视频内容放置到一个mp4容器中) def ffmpegConvertToMp4(inputFilePath, ouputFilePath): global logFile if not os.path.exists(inputFilePath): print(inputFilePath + " 路径不存在!") logFile.write(inputFilePath + " 路径不存在!\n") return False cmd = r'./ffmpeg -i "{0}" -vcodec copy -acodec copy "{1}"'.format(inputFilePath, ouputFilePath) if os.system(cmd) == 0: print(inputFilePath + "转换成功!") logFile.write(inputFilePath + "转换成功!\n") return True else: print(inputFilePath + "转换失败!") logFile.write(inputFilePath + "转换失败!\n") return False # 8、模拟输出进度条 def printProcessBar(sumCount, doneCount, width): precent = doneCount / sumCount useCount = int(precent * width) spaceCount = int(width - useCount) precent = precent*100 print('\t{0}/{1} {2}{3} {4:.2f}%'.format(sumCount, doneCount, useCount*'■', spaceCount*'□', precent), file=sys.stdout, flush=True, end='\r') # m3u8下载器 def m3uVideo8Downloader(): global title global logFile global m3u8Url global cachePath # 1、下载m3u8 print("\t1、开始下载m3u8...") logFile.write("\t1、开始下载m3u8...\n") m3u8Info = getM3u8Info() if m3u8Info is None: return False tsList = [] for playlist in m3u8Info.segments: tsList.append(playlist.uri) # 2、获取key keyText = "" cryptor = None # 判断是否加密 if (len(m3u8Info.keys) != 0) and (m3u8Info.keys[0] is not None): # 默认选择第一个key,且AES-128算法 key = m3u8Info.keys[0] if key.method != "AES-128": print("\t{0}不支持的解密方式!".format(key.method)) logFile.write("\t{0}不支持的解密方式!\n".format(key.method)) return False # 如果key的url是相对路径,加上m3u8Url的路径 keyUrl = key.uri if not keyUrl.startswith("http"): keyUrl = m3u8Url.replace("index.m3u8", keyUrl) print("\t2、开始下载key...") logFile.write("\t2、开始下载key...\n") keyText = getKey(keyUrl) if keyText is None: return False # 判断是否有偏移量 if key.iv is not None: cryptor = AES.new(bytes(keyText, encoding='utf8'), AES.MODE_CBC, bytes(key.iv, encoding='utf8')) else: cryptor = AES.new(bytes(keyText, encoding='utf8'), AES.MODE_CBC, bytes(keyText, encoding='utf8')) # 3、下载ts print("\t3、开始下载ts...") logFile.write("\t3、开始下载ts...\n") if mutliDownloadTs(tsList): print("\tts下载完成---------------------") logFile.write("\tts下载完成---------------------\n") # 4、合并ts print("\t4、开始合并ts...") logFile.write("\t4、开始合并ts...\n") if mergeTs(cachePath, cachePath + "/cache.flv", cryptor, len(tsList)): print("\tts合并完成---------------------") logFile.write("\tts合并完成---------------------\n") else: print(keyText) print("\tts合并失败!") logFile.write("\tts合并失败!\n") return False # 5、开始转换成mp4 print("\t5、开始mp4转换...") logFile.write("\t5、开始mp4转换...\n") if not ffmpegConvertToMp4(cachePath + "/cache.flv", saveRootDirPath + "/" + title + ".mp4"): return False return True if __name__ == '__main__': # 判断m3u8文件是否存在 if not (os.path.exists(m3u8InputFilePath)): print("{0}文件不存在!".format(m3u8InputFilePath)) exit(0) m3u8InputFp = open(m3u8InputFilePath, "r", encoding="utf-8") # 设置error的m3u8 url输出 errorM3u8InfoFp = open(errorM3u8InfoDirPath, "a+", encoding="utf-8") # 设置log file if not os.path.exists(cachePath): os.makedirs(cachePath) logFile = open(logPath, "w+", encoding="utf-8") # 初始化线程池 taskThreadPool = threadpool.ThreadPool(processCountConf) while True: rowData = m3u8InputFp.readline() rowData = rowData.strip('\n') if rowData == "": break m3u8Info = rowData.split(',') title = m3u8Info[0] m3u8Url = m3u8Info[1] try: print("{0} 开始下载:".format(m3u8Info[0])) logFile.write("{0} 开始下载:\n".format(m3u8Info[0])) if m3uVideo8Downloader(): # 成功下载完一个m3u8则清空logFile logFile.truncate() print("{0} 下载成功!".format(m3u8Info[0])) else: errorM3u8InfoFp.write(title + "," + m3u8Url + '\n') errorM3u8InfoFp.flush() print("{0} 下载失败!".format(m3u8Info[0])) logFile.write("{0} 下载失败!\n".format(m3u8Info[0])) except Exception as exception: print(exception) traceback.print_exc() # 关闭文件 logFile.close() m3u8InputFp.close() errorM3u8InfoFp.close() shutil.rmtree(cachePath) print("----------------下载结束------------------") 四、开车姿势与车速展示1、开车姿势①、导入源码用到库m3u8、traceback、threadpool、pycryptodome、beautifulsoup4(用pip3安装就行)②、将 ffmpeg 放到源码文件所在目录(源码调用了cmd命令进而调用了 ffmpeg),创建 input 、output 文件夹Windows / macOS 使用 FFMPEG : https://www.lanzoux.com/b015tjr7eLinux 请到以下网址根据系统自行下载:https://ffmpeg.org/download.html#build-linux修改代码 237 行,win 系统修改为:.\ffmpeg.exe程序目录③、将准备好的 m3u8s_input.txt 文件(必须是utf-8编码,格式如下)保存在 input 文件夹m3u8s_input.txt 示例title_1,m3u8_url_1 title_2,m3u8_url_2 title_3,m3u8_url_3 ...这是省略号,小白别瞎搞... title_n,m3u8_url_n④、配置好源码中的 m3u8 url 文件路径、视频保存的地址、线程数⑤、控制台/终端使用 Python 执行脚本下面将 python 程序保存名为 run.pypython3 run.py注:为了尽量减少频繁的删除、创建 ts 流文件,源码运行时在 output 目录中创建一个cache 缓存目录,里面暂存下载好的 ts 流。下载过程中不可删除!!!全部下载完成会自动删除 cache 缓存目录2、车速展示(系好安全带)五、后记以上源码仅作为Python技术学习、交流之用,切勿用于任何可能造成违法犯罪的场景,否则后果自负!
2020年10月10日
165 阅读
0 评论
0 点赞
2020-09-03
[Python] 喜马拉雅加密算法分析
喜马拉雅加密算法分析 转载于吾爱论坛 wshuo 大大帖子 https://www.52pojie.cn/thread-1255513-1-1.html知识共享蓝奏云:【下载】download.py为主入口文件,其中有两个参数你可能需要调节self.run_t = 7 self.save_dir = r"/Users/kain/Music/喜马拉雅"一个是同时下载个数,一个是保存路径,剩下的需要调节最下面的整本书的albumId了,另外其中我加入了转换mp3格式的函数,不过我默认注释掉了,如果想使用可以配置ffmpeg到环境变量中去前言这几天一直听听评书,发现喜马拉雅上的资源很多,不过很可惜都是付费的,所以我冲了一个月会员,简单写个爬虫,爬下来几10部,够我一年听的了开始分析打开chrome控制台,点击播放,最先拿到的一个接口就是https://mpay.ximalaya.com/mobile/track/pay/244130607/?device=pc当然这个是付费的一部书,所以如果你浏览器不带 会员的cookie是访问不到的,其中的数字 244130607,这个在他们的接口中叫做 trackId, 每个音频文件对应唯一的一个 trackId也就是对应这个界面的后面的数字,通过这个唯一的trackId可以获取到音频文件,那么看一下这个接口返回的内容{ "ret": 0, "msg": "0", "trackId": 244130607, "uid": 170217760, "albumId": 30816438, "title": "《三体》第一季 第十集 聚会与大撕裂", "domain": "http://audiopay.cos.xmcdn.com", "totalLength": 12780565, "sampleDuration": 0, "sampleLength": 0, "isAuthorized": true, "apiVersion": "1.0.0", "seed": 9583, "fileId": "27*31*44*62*1*8*6*48*52*4*6*17*16*6*35*35*6*43*25*27*48*63*58*4*50*47*60*64*15*39*59*49*2*36*48*48*16*58*18*44*2*32*12*7*52*64*51*26*29*4*22*", "buyKey": "617574686f72697a6564", "duration": 1578, "ep": "20NvOoh6T39X3qwKO4cY5g5bVhg+1nfPHIQafFTmCXihnrqF2PjczO8O0auK1KJhDrJ30XMYfKJo2uz+xgwd3rwRPi5f", "highestQualityLevel": 1, "downloadQualityLevel": 1, "authorizedType": 1 }这里,我充会员了,所以可以直接用浏览器中打开这个url,其中有用的字段有了只有几个 seed和 fileId两个通过js加密算法计算出 m4a的路径,并拼接主域名,然后 ep 经过另一个加密算法得到url的访问参数buy_key sign token timestamp,最后将它们拼接到一起才是一个完整的 音频的url两个js加密算法经过我调试我分别找到了这两个加密的 js算法计算 m4a的路径js算法:function vt(t) { this._randomSeed = t, this.cg_hun() } vt.prototype = { cg_hun: function() { this._cgStr = ""; var t = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/\\:._-1234567890" , e = t.length , n = 0; for (n = 0; n < e; n++) { var r = this.ran() * t.length , o = parseInt(r); this._cgStr += t.charAt(o), t = t.split(t.charAt(o)).join("") } }, cg_fun: function(t) { t = t.split("*"); var e = "" , n = 0; for (n = 0; n < t.length - 1; n++) e += this._cgStr.charAt(t[n]); return e }, ran: function() { this._randomSeed = (211 * this._randomSeed + 30031) % 65536; return this._randomSeed / 65536 }, }; c = function(t, e) { var n = new vt(t).cg_fun(e); return "/" === n[0] ? n : "/".concat(n) } console.log(c(9583,"27*31*44*62*1*8*6*48*52*4*6*17*16*6*35*35*6*43*25*27*48*63*58*4*50*47*60*64*15*39*59*49*2*36*48*48*16*58*18*44*2*32*12*7*52*64*51*26*29*4*22*"))用node跑一下可以得到 m4a的路径输出:/group3/M04/9E/88/wKgMbF4ejn2TfGPRAMMEFYoRHXs027.m4a通过ep来计算url参数的js算法:Z = function() { throw new TypeError("Invalid attempt to destructure non-iterable instance") } J = function(t, e) { var n = [] , r = !0 , o = !1 , i = void 0; try { for (var a, u = t[Symbol.iterator](); !(r = (a = u.next()).done) && (n.push(a.value), !e || n.length !== e); r = !0) ; } catch (t) { o = !0, i = t } finally { try { r || null == u.return || u.return() } finally { if (o) throw i } } return n } Q = function(t) { if (Array.isArray(t)) return t } tt = function(t, e) { return Q(t) || J(t, e) || Z() } function yt(t, e) { for (var n, r = [], o = 0, i = "", a = 0; 256 > a; a++) r[a] = a; for (a = 0; 256 > a; a++) o = (o + r[a] + t.charCodeAt(a % t.length)) % 256, n = r[a], r[a] = r[o], r[o] = n; for (var u = o = a = 0; u < e.length; u++) o = (o + r[a = (a + 1) % 256]) % 256, n = r[a], r[a] = r[o], r[o] = n, i += String.fromCharCode(e.charCodeAt(u) ^ r[(r[a] + r[o]) % 256]); return i } var mt = yt("xm", "Ä[üJ=†Û3áf÷N") gt = [19, 1, 4, 7, 30, 14, 28, 8, 24, 17, 6, 35, 34, 16, 9, 10, 13, 22, 32, 29, 31, 21, 18, 3, 2, 23, 25, 27, 11, 20, 5, 15, 12, 0, 33, 26] bt = function(t) { var e1 = yt( function(t, e) { for (var n = [], r = 0; r < t.length; r++) { for (var o = "a" <= t[r] && "z" >= t[r] ? t[r].charCodeAt() - 97 : t[r].charCodeAt() - "0".charCodeAt() + 26, i = 0; 36 > i; i++) if (e[i] == o) { o = i; break } n[r] = 25 < o ? String.fromCharCode(o - 26 + "0".charCodeAt()) : String.fromCharCode(o + 97) } return n.join("") }("d" + mt + "9",gt) , e2 = function(t) { if (!t) return ""; var e, n, r, o, i, a = [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1]; for (o = (t = t.toString()).length, r = 0, i = ""; r < o; ) { do { e = a[255 & t.charCodeAt(r++)] } while (r < o && -1 == e);if (-1 == e) break; do { n = a[255 & t.charCodeAt(r++)] } while (r < o && -1 == n);if (-1 == n) break; i += String.fromCharCode(e << 2 | (48 & n) >> 4); do { if (61 == (e = 255 & t.charCodeAt(r++))) return i; e = a[e] } while (r < o && -1 == e);if (-1 == e) break; i += String.fromCharCode((15 & n) << 4 | (60 & e) >> 2); do { if (61 == (n = 255 & t.charCodeAt(r++))) return i; n = a[n] } while (r < o && -1 == n);if (-1 == n) break; i += String.fromCharCode((3 & e) << 6 | n) } return i }(t) ).split("-") console.log(e1) } var c = bt("20NvOoh6T39X3qwKO4cY5g5bVhg+1nfPHIQafFTmCXihnrqF2PjczO8O0auK1KJhDrJ30XMYfKJo2uz+xgwd3rwRPi5f")这段js比较复杂,调试的时候坑死我了,不在同一个地方,导致我来回复制,最终于才把这个算法整理到这一个js文件中,依然用 node跑一下,输出:[ '617574686f72697a6564', 'ef9a0678d77870843ef203d6333ce021', '5790', '1598533668' ]这几个参数分别对应的是:buy_key sign token timestamp有了这了两个js算法就可以完全的解析 这个接口返回的参数了。python 代码仿写加密算法计算 m4a路径加密算法class vt(): def __init__(self,t): self._randomSeed = t self.cg_hun() def ran(self): self._randomSeed = (211 * self._randomSeed + 30031) % 65536 return self._randomSeed / 65536 def cg_hun(self): self._cgStr = "" t = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/\\:._-1234567890" e = len(t) n = 0 for i in range(e): r = self.ran() * len(t) o = int(r) self._cgStr += t[o] t = "".join(t.split(t[o])) def cg_fun(self,t): t = [int(i) if i else 0 for i in t.split("*")] e = "" n = 0; for n in range(n,len(t)-1): e += self._cgStr[t[n]] return e def path_decode(seed,fileId): c = vt(seed) p = c.cg_fun(fileId) return p if __name__ == '__main__': result = path_decode(9583,"27*31*44*62*1*8*6*48*52*4*6*17*16*6*35*35*6*43*25*27*48*63*58*4*50*47*60*64*15*39*59*49*2*36*48*48*16*58*18*44*2*32*12*7*52*64*51*26*29*4*22*") print(result)通过ep来计算url参数的算法:def yt(t, e): r = [0 for i in range(256)] o = 0 i = "" for a in range(0,256): r[a] = a; for a in range(0,256): o = (o + r[a] + ord(t[a % len(t)])) % 256 n = r[a] r[a] = r[o] r[o] = n u = 0 o = 0 a = 0 for u in range(0,len(e)): a = (a + 1) % 256 o = (o + r[a]) % 256 n = r[a] r[a] = r[o] r[o] = n i += chr(ord(e[u]) ^ r[(r[a] + r[o]) % 256]) return i def bt(t): def arg1(t,e): n = [' ' for i in range(256)] for r in range(0,len(t)): if "a" <= t[r] and "z" >= t[r]: o = ord(t[r]) - 97 else: o = ord(t[r]) - ord("0") + 26 for i in range(0,36): if (e[i] == o): o = i break if 25< o: n[r] = chr(o - 26 + ord("0")) else: n[r] = chr(o + 97) return "".join(n).strip() a1 = arg1("d" + mt + "9", gt) def arg2(t): if not t: return "" e = n = r = o = i = a = [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1]; o = len(t) i = "" r = 0 while r < o: while True: e = a[255 & ord(t[r])] r += 1 if not (r < o and -1 == e): break if (-1 == e): break while True: n = a[255 & ord(t[r])] r += 1 if not (r < o and -1 == n): break if (-1 == n): break i += chr(e << 2 | (48 & n) >> 4) while True: e = (255 & ord(t[r])) if 61 == e: return i r += 1 e = a[e] if not (r < o and -1 == e): break if (-1 == e): break i += chr((15 & n) << 4 | (60 & e) >> 2); while True: n = (255 & ord(t[r])) if (61 == n): return i r += 1 n = a[n] if not (r < o and -1 == n): break if (-1 == n): break i += chr((3 & e) << 6 | n) return i a2 = arg2(t) buy_key,sign,token,timestamp = yt(a1,a2).split('-') data = dict( buy_key=buy_key, sign=sign, token=token, timestamp=timestamp, ) return data mt = yt("xm", "Ä[üJ=†Û3áf÷N") gt = [19, 1, 4, 7, 30, 14, 28, 8, 24, 17, 6, 35, 34, 16, 9, 10, 13, 22, 32, 29, 31, 21, 18, 3, 2, 23, 25, 27, 11, 20, 5, 15, 12, 0, 33, 26] def ep_decode(ep): data = bt(ep) return data if __name__ == '__main__': print(ep_decode('20NvOoh6T39X3qwKO4cY5g5bVhg+1nfPHIQafFTmCXihnrqF2PjczO8O0auK1KJhDrJ30XMYfKJo2uz+xgwd3rwRPi5f'))这个接口到此为止才算是完全可以解析。免费接口分析如果你没有充会员,免费的音频还是可以听的,我找到一个免费音频的接口https://www.ximalaya.com/revision/play/v1/audio?id=324681559&ptype=1返回值:{ "ret": 200, "data": { "trackId": 324681559, "canPlay": true, "isPaid": false, "hasBuy": true, "src": "https://aod.cos.tx.xmcdn.com/group84/M03/4A/A6/wKg5Hl8s0cTwcp6xABQ0EbeuW5Q193.m4a", "albumIsSample": false, "sampleDuration": 48, "isBaiduMusic": false, "firstPlayStatus": true, "isVipFree": false } }这个接口还是比较简单的,返回值里面直接包含 m4a音频地址,没有加密措施,另外 url中的数字依然是 trackId,值得一提的是免费音频的trackId不能用在付费接口,我猜测是版本迭代的问题,或者是客户端不同的问题,因为当时我不只是分析网页的接口,还抓包了电脑客户端的接口,具体对应的是网页还是客户端我也忘了。解析整本书的接口喜马拉雅接口主要关键的有两个参数,一个是前面我说的 trackId 另一个就是albumId,trackId 对应唯一的一个音频,而 albumId 对应的是唯一的一本书。https://www.ximalaya.com/revision/album/v1/getTracksList?albumId=30816438&pageNum=1&pageSize=1000返回值中就有每一集的trackId,其实喜马拉雅还有很多其他接口,搜索接口等等,一般的其他的接口需要在请求头中加入xm-sign,我也写了xm-sign的计算方法:import requests import time import hashlib import random import json from requests.packages.urllib3.exceptions import InsecureRequestWarning requests.packages.urllib3.disable_warnings(InsecureRequestWarning) # 获取sign签名 def get_sign(headers): serverTimeUrl = "https://www.ximalaya.com/revision/time" response = requests.get(serverTimeUrl,headers=headers,verify=False) serverTime = response.text nowTime = str(round(time.time()*1000)) sign = str(hashlib.md5("himalaya-{}".format(serverTime).encode()).hexdigest()) + "({})".format(str(round(random.random()*100))) + serverTime + "({})".format(str(round(random.random()*100))) + nowTime headers["xm-sign"] = sign return headers def get_header(): headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.90 Safari/537.36" } headers = get_sign(headers) return headers if __name__ == '__main__': # 这是一个搜索接口 url = "https://www.ximalaya.com/revision/search/main?core=all&spellchecker=true&device=iPhone&kw=%E9%9B%AA%E4%B8%AD%E6%82%8D%E5%88%80%E8%A1%8C&page=1&rows=20&condition=relation&fq=&paidFilter=false" s = requests.get(url,headers=get_header(),verify=False) print(s.json())还有很多其他接口,我就懒得说了,因为我不想写了,有了这些就可以满足我下载整本书的需求了最终整合我写了 喜马拉雅 扫码登陆的脚本,因为我不能每次都去复制浏览器中的 cookie,这种重复劳动太傻了import requests import re from threading import Thread import time import requests from io import BytesIO import http.cookiejar as cookielib from PIL import Image import sys import psutil from base64 import b64decode import os requests.packages.urllib3.disable_warnings() class show_code(Thread): def __init__(self,data): Thread.__init__(self) self.data = data def run(self): img = Image.open(BytesIO(self.data)) # 打开图片,返回PIL image对象 img.show() def is_login(session): headers = {'User-Agent':"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.89 Safari/537.36"} url = "https://www.ximalaya.com/revision/main/getCurrentUser" try: session.cookies.load(ignore_discard=True) except Exception: pass response = session.get(url,verify=False,headers=headers) if response.json()['ret'] == 200: print(response.json()) return session,True else: return session,False def login(): if not os.path.exists(".cookie"): os.makedirs('.cookie') if not os.path.exists('.cookie/xmly.txt'): print("hello") with open(".cookie/xmly.txt",'w') as f: f.write("") session = requests.session() session.cookies = cookielib.LWPCookieJar(filename='.cookie/xmly.txt') session,status = is_login(session) if not status: url = "https://passport.ximalaya.com/web/qrCode/gen?level=L" response = session.get(url,verify=False) data = response.json() # with open('qrcode.jpg','wb') as f: # f.write(b64decode(data['img'])) t= show_code(b64decode(data['img'])) t.start() qrId = data['qrId'] url = 'https://passport.ximalaya.com/web/qrCode/check/%s/%s' % (qrId,int(time.time()*1000)) while 1: response = session.get(url,verify=False) data = response.json() # code = re.findall("window.wx_code='(.*?)'",response.text) # sys.exit() if data['ret'] == 0: # for proc in psutil.process_iter(): # 遍历当前process # try: # if proc.name() == "Microsoft.Photos.exe": # proc.kill() # 关闭该process # except Exception as e: # print(e) break time.sleep(1) session.cookies.save() return session if __name__ == '__main__': login()简单的一个扫码登陆脚本,如果cookie自动保存成文件,下次使用的时候直接调用:session = login()就能在保持登陆状态下,访问各种接口
2020年09月03日
262 阅读
0 评论
0 点赞
2020-09-01
【python】ncm解密
参考 https://github.com/ix64/unlock-music 发布的js代码,改写为python代码,并做少量修改运行结果解密py目录下所有ncm文件Codeimport base64 import json import os from mutagen.id3 import ID3, TIT2, TPE1, TALB from Crypto.Cipher import AES def getUint32(dataView, offset): dataView = list(dataView[offset:offset+4])[::-1] u16 = '' for each in dataView: u16 += hex(each)[-2:] u16 = u16.replace('x', '0') return int(u16, 16) def getKeyData(dataView, offset): keyLen = getUint32(dataView, offset) offset += 4 cipherText = list(dataView[offset:offset+keyLen]) for i in range(0, len(cipherText)): cipherText[i] = cipherText[i] ^ 0x64 offset += keyLen cryptor = AES.new(key=bytes.fromhex('687a4852416d736f356b496e62617857'), mode=AES.MODE_ECB) plainText = cryptor.decrypt(bytes(cipherText)) return {"offset": offset, "data": list(plainText)[17:-14]} def getKeyBox(keyData): box = [] for i in range(0, 256): box.append(i) keyDataLen = len(keyData) j = 0 for i in range(0, 256): j = (box[i] + j + keyData[i % keyDataLen]) & 0xff box[i], box[j] = box[j], box[i] boxmap = [] for i in range(0, 256): i = (i + 1) & 0xff; si = box[i] sj = box[(i + si) & 0xff] boxmap.append(box[(si + sj) & 0xff]) return boxmap def getMetaData(dataView, offset): metaDataLen = getUint32(dataView, offset) offset += 4 cipherText = list(dataView[offset:offset+metaDataLen]) for i in range(0, len(cipherText)): cipherText[i] = cipherText[i] ^ 0x63 offset += metaDataLen cryptor = AES.new(key=bytes.fromhex('2331346C6A6B5F215C5D2630553C2728'), mode=AES.MODE_ECB) plainText = cryptor.decrypt(base64.b64decode(bytes(cipherText)[22:])).decode() labelIndex = plainText.find(":") result = json.loads(plainText[labelIndex+1:].encode().replace(b'\x07', b'').decode()) return {'data': result, 'offset': offset} def GetFileInfo(artist, title, filenameNoExt, separator): newArtist = "" newTitle = "" filenameArray = filenameNoExt.split(separator) if len(filenameArray) > 1: newArtist = filenameArray[0].strip() newTitle = filenameArray[1].strip() elif len(filenameArray) == 1: newTitle = filenameArray[0].strip() if type(artist) == 'string' and artist: newArtist = artist if type(title) == 'string' and title: newArtist = title return {'artist': newArtist, 'title': newTitle} def DetectAudioExt(data, fallbackExt): if data[:8] == '664C6143': return 'flac' elif data[:6] == '494433': return 'mp4' elif dat[:8] == '4F676753': return 'ogg' elif data[4:12] == '66747970': return 'm4a' elif data[:32] == '3026B2758E66CF11A6D900AA0062CE6C': return 'wma' elif data[:8] == '52494646': return 'wav' else: return 'mp3' def decryptncm(filepath): outputpath = './unlockmusic/wangyiyun/' if not os.path.exists(outputpath): os.makedirs(outputpath) ncm = {} ncm['fileext'] = filepath.split('.')[-1] ncm['filename'] = filepath[:-(len(ncm['fileext'])+1)] with open(filepath, 'rb') as f: ncm['filebuffer'] = f.read() keyDataObj = getKeyData(ncm['filebuffer'], 10) keyBox = getKeyBox(keyDataObj['data']) musicMetaObj = getMetaData(ncm['filebuffer'], keyDataObj['offset']); musicMeta = musicMetaObj['data'] audioOffset = musicMetaObj['offset']+getUint32(ncm['filebuffer'], musicMetaObj['offset']+5)+13 audioData = list(ncm['filebuffer'][audioOffset:]) lenAudioData = len(audioData) for i in range(0, lenAudioData): audioData[i] ^= keyBox[i & 0xff]; if not 'album' in musicMeta.keys(): musicMeta['album'] = '' artists = [] if musicMeta['artist']: for each in musicMeta['artist']: artists.append(each[0]) info = GetFileInfo(' & '.join(artists), musicMeta['musicName'], ncm['filename'], '-') if not artists: artists.append(info['artist']) if not 'format' in musicMeta.keys(): musicMeta['format'] = DetectAudioExt(audioData[:32].hex().upper(), "mp3"); outputpath = outputpath+musicMeta['musicName']+"."+musicMeta['format'] with open(outputpath, 'wb') as f: f.write(bytes(audioData)) if musicMeta['format'] == 'mp3': audio = ID3(outputpath) audio.update_to_v23() audio.delete() audio['TIT2'] = TIT2(encoding=3, text=[info['title']]) audio['TPE1'] = TPE1(encoding=3, text=[artists[0]]) audio['TALB'] = TALB(encoding=3, text=[musicMeta['album']]) audio.save() print(musicMeta['musicName']+"."+musicMeta['format']+'解密完成') def main(): for each in os.listdir(os.getcwd()): if '.ncm' in each: decryptncm(each) a = input('所有音乐解密完成') if __name__ == "__main__": main()已知问题自身语法写的比较低级,影响运行速度当解密文件过多时,时间偏长
2020年09月01日
808 阅读
0 评论
0 点赞
2020-08-03
[Python] 影长求经纬度
一个由影长和时间计算经纬度的py脚本,如果信息只有一张图片,只要有拍摄时间和阴影,参考这个小脚本,方便计算经纬度坐标,减少时区时间经纬度之间的换算过程#!/usr/bin/env python #-*-coding:utf-8-*- #coding=UTF-8 #test version import math #参数变量 ns =180#纬度 ew = 360#经度 ns_z=0#直射纬度 ew_z=0#直射经度 jingducha =0#经度差 weiducha =0#纬度差 weidu_y=0 jingdu_y=0 date_m = input("请输入影子长度测量日期(月):") date_d = input("请输入影子长度测量日期(日):") sq_ew = int(input("请输入时区(东:1,西:2):")) sq_ji = int(input("请输入时区数:")) time_h = float(input("请输入影子测量时间(时):")) time_m = float(input("请输入影子测量时间(分):")) sun = float(input("请输入影子方向(时钟秒针数字表示指向):")) long = float(input("请输入影子长度比值(影子长度/实际长度):")) #时区换算 sq = 0 if sq_ew == 1: sq = sq_ji+12 if sq == 24: sq = sq-24 if sq_ew == 2: sq = 12-sq_ji #时间日期换算(东西12区为基准) time = time_h*60 + time_m - sq*60 if time < 0: time = time + 1440 date_d = int(date_d) -1 date = int(date_m)*30.5 + int(date_d) #计算太阳直射经度 if time < 720: time = time+1440 ew = ((time-720)/1440)*360 #输出太阳直射经度 if ew<180: ew_z=180-ew print("太阳直射经度为:\n西经") print(ew_z) if ew>=180: ew_z=ew-180 print("太阳直射经度为:\n东经") print(ew_z) #计算太阳直射纬度 if date < 83: date = date+365 date_bili=(date-83)/91.25 if date_bili<1: ns = 90 - date_bili*23.433333333333334 if 1<=date_bili<=3: ns = 90+(date_bili-2)*23.433333333333334 if date_bili>3: ns = 90 - (date_bili-4)*23.433333333333334 #输出太阳直射纬度 if ns>90: ns_z=ns-90 print("太阳直射纬度为:\n南纬") print(ns_z) if ns<=90: ns_z=90-ns print("太阳直射纬度为:\n北纬") print(ns_z) #计算偏移夹角 jiajiao = 90 - math.degrees(math.atan(long)) #太阳夹角 #计算影子经纬度差 weiducha=math.cos(math.radians(sun*6)) jingducha=math.sin(math.radians(sun*6)) #计算影子经纬度 weidu = ns - weiducha jingdu = ew + jingducha #输出影子经纬度 if jingdu<=0: jingdu=jingdu+360 if jingdu>360: jingdu=jingdu-360 if jingdu<180: jingdu_y=180-jingdu print("影子所在经度为:\n西经") print(jingdu_y) if jingdu>=180: jingdu_y=jingdu-180 print("影子所在经度为:\n东经") print(jingdu_y) if weidu<0: weidu=0-weidu if weidu>180: weidu=180-(weidu-180) if weidu>90: weidu_y=weidu-90 print("影子所在纬度为:\n南纬") print(weidu_y) if weidu<=90: weidu_y=90-weidu print("影子所在纬度为:\n北纬") print(weidu_y)
2020年08月03日
576 阅读
0 评论
0 点赞
2020-07-27
无需服务器,哔哩哔哩直播自动签到
哔哩哔哩直播自动签到哔哩哔哩直播自动签到仓库地址:https://github.com/t00t00-crypto/bilibili-actionGithub Actions 部署指南一、Fork 此仓库二、设置账号COOKIE先登录B站,然后访问https://api.live.bilibili.com/sign/GetSignInfo,按F12,在Network中刷新复制cookie中的内容,从sid开始至infoc(可能有所不同)在GitHub中添加名为 COOKIE的变量,值为复制的内容。Settings-->Secrets-->New secret支持多账号,账号COOKIE之间用 # 分隔示例:COOKIE:sid=97d0....#sid=97d0....三、启用 Action点击 Actions,再点击 I understand my workflows, go ahead and enable them点击左侧的 Star四、查看运行结果Actions --> 签到 --> build能看到如下图所示,表示成功注意事项每天运行两次,在上午 6 点和晚上 22 点。可以通过 Star 手动启动一次。P.S.使用本项目*不会*导致你的用户名或密码泄露设定启动时间可以在项目的 /.github/workflows* 文件夹下打开 run.yml* 并编辑修改第9行(注意:*时间是 UTC 时间,与北京时间时差为 +8*),即北京时间早上6点为 UTC 时间 22点禁止其他人 Star 导致签到误启动在15行下面加入if: github.event.repository.owner.id == github.event.sender.id保存修改
2020年07月27日
679 阅读
0 评论
0 点赞
2020-07-27
无需服务器,天翼云盘自动签到 + 抽奖
天翼云盘自动签到 + 抽奖无需服务器,天翼云盘自动签到 + 抽奖,每月扩容3G+仓库地址:https://github.com/t00t00-crypto/cloud189-action源代码来自:https://51.ruyo.net/16050.html,仅做了一些修改Github Actions 部署指南一、Fork 此仓库二、设置账号密码添加名为 USER、PWD 的变量,值分别为 账号(仅支持手机号)、密码 Settings-->Secrets-->New secret支持多账号,账号之间与密码之间用 # 分隔,账号与密码的个数要对应示例:USER:13800000000#13800000001,PWD:cxkjntm#jntmcxk三、启用 Action点击 Actions,再点击 I understand my workflows, go ahead and enable them点击左侧的 Star四、查看运行结果Actions --> 签到 --> build能看到如下图所示,表示成功注意事项每天运行两次,在上午 6 点和晚上 22 点。可以通过 Star 手动启动一次。P.S.使用本项目*不会*导致你的用户名或密码泄露设定启动时间可以在项目的 /.github/workflows* 文件夹下打开 run.yml* 并编辑修改第9行(注意:*时间是 UTC 时间,与北京时间时差为 +8*),即北京时间早上6点为 UTC 时间 22点禁止其他人 Star 导致签到误启动在15行下面加入if: github.event.repository.owner.id == github.event.sender.id保存修改
2020年07月27日
204 阅读
0 评论
0 点赞
2020-07-24
python实现逢七拍腿游戏
题目几个好朋友一起玩逢七拍腿游戏,即从1开始依次数数,当数到尾数是7的数或7的倍数时,则不报出该数,而是拍一下腿。现在编写程序,从1数到99,假设每个人都没有出错,计算一下共要拍多少次腿,通过编程求出结果。控制台实现情况1控制台实现情况2(符合条件才可以进行下一步,否则循环到符合条件为止)代码# coding: utf-8 print("这是一个三人规模的《逢\'7\'拍腿》的小游戏(即遇到7的倍数或个位为7的数就拍腿一次),接下来开始游戏。") counta = 1 while counta > 0: ref = input("确认次数范围。请输入数值范围的最后一个整数,已默认由1开始:") ref = int(ref) if ref > 0: counta = 0 countb = 1 while countb > 0: num = input("确认结束时拍数。请输入你刚才设置区域内的一个整数,以其作为游戏结束时的数值:") num = int(num) if num in range(1, ref + 1): countb = 0 countc = 1 while countc > 0: role = input("请选择一个角色。第1位开始的a,第二位开始的b,第三位开始的c:") if role in ("a", "b", "c"): countc = 0 sum = 0 if role == "a": numa = 1 while numa <= num: if numa % 7 == 0 or numa % 10 == 7: sum += 1 numa += 3 if role == "b": numb = 1 while numb <= num: if numb % 7 == 0 or numb % 10 == 7: sum += 1 numb += 3 if role == "c": numc = 1 while numc <= num: if numc % 7 == 0 or numc % 10 == 7: sum += 1 numc += 3 else: countc += 1 else: countb += 1 else: counta += 1 print("你选择的角色%s最终拍腿次数为%d,游戏结束!" % (role, sum))
2020年07月24日
261 阅读
0 评论
0 点赞
1
2
3