├── .gitignore ├── 0x01-监控反代pixiv链接 ├── 0x01监听剪贴板自动反代.py ├── Readme.md └── img │ └── 1.png ├── 0x02-花火学园邀请码脚本 ├── Readme.md ├── huahuo.py ├── img │ ├── 1.png │ └── 2.png └── requirement.txt ├── 0x03-iwara下载脚本 ├── Readme.md ├── img │ ├── 1.png │ ├── 2.png │ └── 3.png ├── iwara.py ├── requirement.txt └── thread_pool.py ├── 0x04-mzitu爬虫 ├── Readme.md ├── img │ ├── 1.png │ └── 2.png ├── mzitu.py └── requirement.txt ├── 0x05-绅士小站 ├── Readme.md ├── gca.py ├── img │ ├── 1.jpg │ ├── 2.png │ └── 3.png └── requirement.txt ├── 0x06-pixiv动图合成 ├── Readme.md ├── img │ ├── 1.png │ └── 2.png ├── pix.py └── requirement.txt ├── 0x07-绅士仓库 ├── .gitignore ├── Config │ ├── __init__.py │ ├── cangku.sql │ ├── gca_tw.sql │ └── openssl.cnf ├── Kit │ ├── __init__.py │ ├── chrome.py │ └── kit.py ├── Pipfile ├── Pipfile.lock ├── login.py └── main.py ├── 0x08-绅士小站2 ├── .gitignore ├── Config │ ├── __init__.py │ └── openssl.cnf ├── Kit │ ├── __init__.py │ ├── chrome.py │ └── kit.py ├── Pipfile ├── Pipfile.lock └── main.py ├── 0x09-Pixiv插画tag数据导入Eagle ├── Home.library │ ├── images │ │ ├── ffe126de-87b1-915b-a0c3-6032b9343bbb.info │ │ │ ├── 86352163-1.jpg │ │ │ ├── 86352163-1_thumbnail.png │ │ │ └── metadata.json │ │ ├── ffea5831-3817-f97f-c375-b5c38ebaa890.info │ │ │ ├── 76315323.jpg │ │ │ ├── 76315323_thumbnail.png │ │ │ └── metadata.json │ │ ├── ffeaeaaa-e475-8741-663e-c4a3308c6e3b.info │ │ │ ├── 89193684.jpg │ │ │ ├── 89193684_thumbnail.png │ │ │ └── metadata.json │ │ ├── ffeb50a5-5447-a4fc-cc95-a009fc385fbd.info │ │ │ ├── 84372116.jpg │ │ │ ├── 84372116_thumbnail.png │ │ │ └── metadata.json │ │ ├── ffebbbbf-0a8f-732d-b196-e36fa2a12b9e.info │ │ │ ├── 70899400.png │ │ │ ├── 70899400_thumbnail.png │ │ │ └── metadata.json │ │ ├── ffebbe70-01e0-4ef0-9060-c9380f4aabc4.info │ │ │ ├── 92016561-0.jpg │ │ │ ├── 92016561-0_thumbnail.png │ │ │ └── metadata.json │ │ ├── fff07694-c82e-e1b8-c7b3-9c7e4cb5d2a3.info │ │ │ ├── 73190808-0.jpg │ │ │ ├── 73190808-0_thumbnail.png │ │ │ └── metadata.json │ │ ├── fff4c7d0-7060-e34d-1dc5-cdb85cb058c9.info │ │ │ ├── 83619652-11.jpg │ │ │ ├── 83619652-11_thumbnail.png │ │ │ └── metadata.json │ │ ├── fff5e73d-460c-fbad-e90a-3080c6a7a923.info │ │ │ ├── 88199924.jpg │ │ │ ├── 88199924_thumbnail.png │ │ │ └── metadata.json │ │ └── fff75130-4feb-2d23-24bc-1b098faab96e.info │ │ │ ├── 82329785.png │ │ │ ├── 82329785_thumbnail.png │ │ │ └── metadata.json │ ├── metadata.json │ └── tags.json ├── Readme.md ├── img │ ├── 0.png │ ├── 1.png │ ├── 2.png │ ├── 3.png │ └── 4.png ├── pixiv2eagle.py ├── requirement.txt └── thread_pool.py ├── 0x0a-碧蓝航线-静态立绘解密加密工具 ├── Readme.md ├── Test │ ├── Mesh │ │ └── beierfasite_4-mesh.obj │ ├── Picture │ │ └── beierfasite_4.png │ └── Texture2D │ │ └── beierfasite_4.png ├── blhx_img_tool.py ├── img │ ├── 0.png │ ├── 1.png │ ├── 2.png │ └── 3.png ├── requirement.txt └── 解封包_0补偿与0.5补偿对比测试结果 │ ├── test_result.txt │ ├── 封包_+0.5_×_明显白线区域_zhenhai_2.png │ ├── 封包_+0.5_√_与游戏内拆包文件 一致_beierfasite_4.png │ ├── 封包_+0_√_zhenhai_2.png │ ├── 封包_+0_√_与游戏内的拆包出的文件对比,有些微白线_beierfasite_4.png │ ├── 测试图片 │ ├── Picture │ │ ├── beierfasite_4.png │ │ └── zhenhai_2.png │ └── Texture2D │ │ ├── beierfasite_4.png │ │ └── zhenhai_2.png │ ├── 解包_+0.5_√_beierfasite_4.png │ ├── 解包_+0.5_√_zhenhai_2.png │ ├── 解包_+0_×_左下角与右下角有白色横线_zhenhai_2.png │ └── 解包_+0_×_明显白线区域_beierfasite_4.png ├── 0x0b-批量拖拽-文件后缀名重命名工具 ├── Readme.md ├── exe │ └── FileRenamer v1.1.exe ├── filerenamer.py └── img │ └── 0.png ├── LICENSE └── Readme.md /.gitignore: -------------------------------------------------------------------------------- 1 | ##### Tools project .gitignore Start ##### 2 | # log 3 | log/* 4 | 5 | ##### Tools project .gitignore End ##### 6 | 7 | 8 | ##### Github Python.gitignore Start ##### 9 | # Byte-compiled / optimized / DLL files 10 | __pycache__/ 11 | *.py[cod] 12 | *$py.class 13 | 14 | # C extensions 15 | *.so 16 | 17 | # Distribution / packaging 18 | .Python 19 | build/ 20 | develop-eggs/ 21 | dist/ 22 | downloads/ 23 | eggs/ 24 | .eggs/ 25 | lib/ 26 | lib64/ 27 | parts/ 28 | sdist/ 29 | var/ 30 | wheels/ 31 | share/python-wheels/ 32 | *.egg-info/ 33 | .installed.cfg 34 | *.egg 35 | MANIFEST 36 | 37 | # PyInstaller 38 | # Usually these files are written by a python script from a template 39 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 40 | *.manifest 41 | *.spec 42 | 43 | # Installer logs 44 | pip-log.txt 45 | pip-delete-this-directory.txt 46 | 47 | # Unit test / coverage reports 48 | htmlcov/ 49 | .tox/ 50 | .nox/ 51 | .coverage 52 | .coverage.* 53 | .cache 54 | nosetests.xml 55 | coverage.xml 56 | *.cover 57 | *.py,cover 58 | .hypothesis/ 59 | .pytest_cache/ 60 | cover/ 61 | 62 | # Translations 63 | *.mo 64 | *.pot 65 | 66 | # Django stuff: 67 | *.log 68 | local_settings.py 69 | db.sqlite3 70 | db.sqlite3-journal 71 | 72 | # Flask stuff: 73 | instance/ 74 | .webassets-cache 75 | 76 | # Scrapy stuff: 77 | .scrapy 78 | 79 | # Sphinx documentation 80 | docs/_build/ 81 | 82 | # PyBuilder 83 | .pybuilder/ 84 | target/ 85 | 86 | # Jupyter Notebook 87 | .ipynb_checkpoints 88 | 89 | # IPython 90 | profile_default/ 91 | ipython_config.py 92 | 93 | # pyenv 94 | # For a library or package, you might want to ignore these files since the code is 95 | # intended to run in multiple environments; otherwise, check them in: 96 | # .python-version 97 | 98 | # pipenv 99 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 100 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 101 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 102 | # install all needed dependencies. 103 | #Pipfile.lock 104 | 105 | # poetry 106 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 107 | # This is especially recommended for binary packages to ensure reproducibility, and is more 108 | # commonly ignored for libraries. 109 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 110 | #poetry.lock 111 | 112 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 113 | __pypackages__/ 114 | 115 | # Celery stuff 116 | celerybeat-schedule 117 | celerybeat.pid 118 | 119 | # SageMath parsed files 120 | *.sage.py 121 | 122 | # Environments 123 | .env 124 | .venv 125 | env/ 126 | venv/ 127 | ENV/ 128 | env.bak/ 129 | venv.bak/ 130 | 131 | # Spyder project settings 132 | .spyderproject 133 | .spyproject 134 | 135 | # Rope project settings 136 | .ropeproject 137 | 138 | # mkdocs documentation 139 | /site 140 | 141 | # mypy 142 | .mypy_cache/ 143 | .dmypy.json 144 | dmypy.json 145 | 146 | # Pyre type checker 147 | .pyre/ 148 | 149 | # pytype static type analyzer 150 | .pytype/ 151 | 152 | # Cython debug symbols 153 | cython_debug/ 154 | 155 | # PyCharm 156 | # JetBrains specific template is maintainted in a separate JetBrains.gitignore that can 157 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 158 | # and can be added to the global gitignore or merged into this file. For a more nuclear 159 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 160 | #.idea/ 161 | 162 | ##### Github Python.gitignore End ##### -------------------------------------------------------------------------------- /0x01-监控反代pixiv链接/0x01监听剪贴板自动反代.py: -------------------------------------------------------------------------------- 1 | import win32clipboard as wc 2 | import time 3 | import re 4 | 5 | """ 6 | __author__:Coder-Sakura 7 | __time__:2020/07/06 00:44:53 8 | 监听所复制的pixiv插画地址,并将其反代成无需梯子即可访问的反代链接. 9 | 比如: 10 | 在pixiv复制了https://i.pximg.net/img-master/img/2020/07/01/21/08/21/82688845_p0_master1200.jpg 11 | 脚本会转换为https://i.pixiv.cat/img-master/img/2020/07/01/21/08/21/82688845_p0_master1200.jpg 12 | 会自动拷贝到剪贴板. 13 | """ 14 | 15 | #获取粘贴板里的内容 16 | def getCopyTxet(): 17 | # 异常则关闭,防止因为复制文件/文件夹/图片/视频等非文本类型文件而崩溃 18 | wc.OpenClipboard() 19 | try: 20 | copytxet = wc.GetClipboardData() 21 | except: 22 | wc.CloseClipboard() 23 | else: 24 | wc.CloseClipboard() 25 | return copytxet 26 | 27 | def setText(text): 28 | wc.OpenClipboard() 29 | wc.EmptyClipboard() 30 | wc.SetClipboardText(text) 31 | wc.CloseClipboard() 32 | 33 | def r_sub(text): 34 | return re.sub(r"pximg.net","pixiv.cat",text) 35 | 36 | if __name__ == '__main__': 37 | # 存储上次剪切板内容 38 | last_data = None 39 | while True: 40 | # 轮询 41 | time.sleep(1) 42 | data = getCopyTxet() 43 | if data == None: 44 | continue 45 | 46 | if data != last_data: 47 | # 如果内容和上次的不同 48 | print("剪贴板内容为:\n" + data,'\n') 49 | 50 | if "https://i.pximg.net" in data: 51 | data = r_sub(data) 52 | setText(data) 53 | print("反代链接替换成功:",data,'\n') 54 | 55 | last_data = data -------------------------------------------------------------------------------- /0x01-监控反代pixiv链接/Readme.md: -------------------------------------------------------------------------------- 1 | **自动替换pixiv链接为反代链接** 2 | 3 | --- 4 | 5 | 监听文本复制并替换成可直接访问的反代链接 6 | 7 | --- 8 | 9 | 使用方法 10 | 11 | ``` 12 | python 0x01监听剪贴板自动反代.py 13 | ``` 14 | 15 | 复制:https://i.pximg.net/c/360x360_70/img-master/img/2021/02/14/22/20/18/87780332_p0_square1200.jpg 16 | 17 | 自动替换为:https://i.pixiv.cat/c/360x360_70/img-master/img/2021/02/14/22/20/18/87780332_p0_square1200.jpg 18 | 19 | 20 | 21 | 运行截图 22 | 23 | ![](./img/1.png) 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /0x01-监控反代pixiv链接/img/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WriteCode-ChangeWorld/Tools/096dd8af8b9d1f501187ce62ab38e6be07f1518d/0x01-监控反代pixiv链接/img/1.png -------------------------------------------------------------------------------- /0x02-花火学园邀请码脚本/Readme.md: -------------------------------------------------------------------------------- 1 | ## 花火学园注册邀请码脚本 2 | 3 | --- 4 | 5 | 使用方法: 6 | 7 | ![img](./img/1.png) 8 | 9 | 10 | 11 | 如果题目不在题库,则会这样显示: 12 | 13 | ![img](./img/2.png) 14 | 15 | -------------------------------------------------------------------------------- /0x02-花火学园邀请码脚本/huahuo.py: -------------------------------------------------------------------------------- 1 | import requests,json 2 | import random 3 | 4 | url ="https://www.say-huahuo.com/qa.php" 5 | 6 | headers = { 7 | "content-type":"application/json;charset=UTF-8", 8 | "user-agent":"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.25 Safari/537.36 Core/1.70.3741.400 QQBrowser/10.5.3863.400", 9 | } 10 | 11 | se = requests.session() 12 | 13 | doc = [ 14 | {"title":"下列哪个数据类型占用内存空间最少","answer":"Byte"}, 15 | {"title":"以下哪个不是世萌萌王获得者","answer":"木之本樱"}, 16 | {"title":"この世の果てで恋を唄う少女YU-NO里的主角有马拓也被称为?","answer":"行走的性欲"}, 17 | {"title":"key社的春夏冬秋四季分别是,CLANNAD、AIR、Kanon和____。 ","answer":"ONE~辉之季节~"}, 18 | {"title":"《北斗神拳》中不属于健次郎的奥义的是","answer":"无明封杀阵"}, 19 | {"title":"动画《干物妹!小埋》中,小埋的游戏名称是什么","answer":"UMR"}, 20 | {"title":"《尘骸魔京》中追风者寻找的夫婿就是","answer":"九门克绮"}, 21 | {"title":"《黑执事》中,伊丽莎白是","answer":"剑术天才"}, 22 | {"title":"《黑执事》中称呼女装的夏尔为美丽小巧的知更鸟的是","answer":"阿雷斯特·钱帕"}, 23 | {"title":"《近月少女的礼仪》中人物的名字与以下哪个相关","answer":"银行"}, 24 | {"title":"《名侦探柯南》《少年侦探团》《文豪野犬》这三部动画都与以下那位作家有关联","answer":"太宰治"}, 25 | {"title":"《天书奇谭》中刚出生的蛋生一开始吃了什么","answer":"大饼"}, 26 | {"title":"ChuSingura46+1中谁拯救了陷入绝望的直刃","answer":"主税"}, 27 | {"title":"CLANNAD有多少集","answer":"49"}, 28 | {"title":"在第三部jojo中DIO的the World最终暂停了几秒?","answer":"九秒"}, 29 | {"title":"以下哪个动画不是游戏改编的?","answer":"2008年P.A.WORKS的真实之泪"}, 30 | {"title":"中二社因为那部作品转变为巨乳社的?","answer":"sppl"}, 31 | {"title":"下列哪一位是G吧大吧主?","answer":"ACG之神"}, 32 | {"title":"请问论坛的通用解压密码是?","answer":"Say花火"}, 33 | {"title":"下面哪个 jQuery 方法用于隐藏被选元素?","answer":"display(none)"}, 34 | {"title":"传颂之物 二人的白皇中旧人类怎么样了?","answer":"没救了,都变树了"}, 35 | {"title":"死に逝く君、館に芽吹く憎悪中,上位种族的弱点是什么?","answer":"血液"}, 36 | {"title":"老虚是____。","answer":"爱的战士"}, 37 | {"title":"动画《干物妹!小埋》中,被网友调侃为GBA的是在哪个场景?","answer":"开车去海边的时候"}, 38 | {"title":"一休哥的法名是?","answer":"一休宗纯"}, 39 | {"title":"《らぶらぶシスターズ ~花嫁&姉妹達とのドキドキハーレム生活》中H了就会变瘦的妹子是?","answer":"模特妹妹"}, 40 | {"title":"这幅画的作者是谁?","answer":"梵高"}, 41 | {"title":"蹭的累是什么的谐音","answer":"腹黑"}, 42 | {"title":"财团b指的是?","answer":"万代"}, 43 | {"title":"正田崇是什么系的剧本家?","answer":"燃系"}, 44 | {"title":"被誉为为神仙水的是?","answer":"SK-II"}, 45 | {"title":"奇迹的世代不包括下面的谁?","answer":"火神大我"}, 46 | {"title":"駄作中四个女主角中,哪一位是渴求别人的爱的怪物?","answer":"菜々ヶ木アリス"}, 47 | {"title":"在Linux中,哪一个标记可以把该命令(组)作为子进程","answer":"&"}, 48 | {"title":"下面哪个声优的给初音未来的歌曲提供了舞蹈动作?","answer":"小仓唯"}, 49 | {"title":"以下哪个作品不含ntr?","answer":"纯白交响曲"}, 50 | {"title":"哆啦A梦原来是啥颜色?","answer":"黄色"}, 51 | {"title":"以下哪个角色出自约会大作战?","answer":"时崎狂三"}, 52 | {"title":"请问论坛的学园长是?","answer":"Say花火"}, 53 | {"title":"百合同人作品花吻在上的20部正作中,哪部于2010年8月被改编成OVA动画?","answer":"3"}, 54 | {"title":"蜥蜴の尻尾切り中男主为什么痴迷于人体再生?","answer":"女朋友得癌症了"}, 55 | {"title":"游戏Myself ; Yourself中若月修辅是____之一。","answer":"男主"}, 56 | {"title":"在 PHP 中,所有的变量以哪个符号开头?","answer":"$"}, 57 | {"title":"以下不是国产gal的是?","answer":"格林花园的少女"}, 58 | {"title":"自称superai的人工智能叫什么名字?","answer":"绊爱"}, 59 | {"title":"战极姬系列里哪部有NTR的感觉?","answer":"战极姬6"}, 60 | {"title":"爱衣酱大胜利出自哪部番?","answer":"我的女友和青梅竹马的惨烈修罗场"}, 61 | {"title":"g弦上的魔王被称为?","answer":"赌上性命的——纯爱"}, 62 | {"title":"幸福噩梦中主人公前期靠什么识别梦和现实?","answer":"眼镜"}, 63 | {"title":"gore screaming show里ユカ的真名是?","answer":"音无紫"}, 64 | {"title":"以下不属于米泽円配过的音是","answer":"樱木由加利"}, 65 | {"title":"鬼哭街中男主的绰号是?","answer":"紫电掌"}, 66 | {"title":"下列每条选项的声优中,所属事务所完全不同的是","answer":"佐仓绫音 东山央奈 种田梨沙"}, 67 | {"title":"清水爱不光是一位声优也是一名职业运动员,那么这门项目是什么?","answer":"体操"}, 68 | {"title":"多啦A梦的体重是多少?","answer":"129.3斤"}, 69 | {"title":"动画《山田君与7人魔女》里白石丽的能力是?","answer":"交换身体"}, 70 | {"title":"在Fate HF线中,土狼说他喜欢谁?","answer":"樱"}, 71 | {"title":"国内玩家对于《夏娃年代纪》里大公主的争议主要原因为?","answer":"ntr"}, 72 | {"title":"贾宝玉身上佩戴的玉叫啥?","answer":"通灵宝玉"}, 73 | {"title":"下面哪一部没有和泉万夜参与脚本?","answer":"maggot biats"}, 74 | {"title":"下面哪一部不是钟表社的作品?","answer":"maggot biats"}, 75 | {"title":"传颂之物中,谁和谁去开店了?","answer":"藤香和卡米拉"}, 76 | {"title":"观沧海的作者是?","answer":"曹操"}, 77 | {"title":"装甲恶鬼村正英雄篇中最后村正最后成为了谁的剑?","answer":"一条"}, 78 | {"title":"哪个作品与田中罗密欧,龙骑士07,都乃河勇人三人都相关?","answer":"rewrite"}, 79 | {"title":"日本同人漫画家二阶堂みつき和哪位漫画家关系最近?","answer":"大岛永远"}, 80 | {"title":"苍翼默示录动漫是游戏里哪一部的主线剧情?","answer":"厄运扳机"}, 81 | {"title":"以下那部漫画不是BL向作品?","answer":"累"}, 82 | {"title":"下面那个不是新十年的假面骑士?","answer":"假面骑士Decade"}, 83 | {"title":"渡りの诗是一首什么语言的歌?","answer":"包含多种"}, 84 | {"title":"纸上魔法使中哪一个是?","answer":"游行寺夜子"}, 85 | {"title":"恋樱中带来幸福的死神是?","answer":"缇娜"}, 86 | {"title":"游戏《秘密の花园》里,女主是?","answer":"母亲和父亲生的"}, 87 | {"title":"以下哪个加藤不存在于三次元?","answer":"加藤断"}, 88 | {"title":"以下哪个人物和佐伯克哉出现在同一款游戏里?","answer":"御堂孝典"}, 89 | {"title":"以下哪个是Cross Days里的可攻略对象?","answer":"伊藤诚"}, 90 | {"title":"以下哪个不是百合作品?","answer":"処女はお姉さまに恋してる"}, 91 | {"title":"以下哪个不是elf会社的作品?","answer":"トラク=ナクア."}, 92 | {"title":"以下四个角色哪个喜欢女性?","answer":"佐藤圣"}, 93 | {"title":"多啦A梦2012年剧场版中出现的稀有独角仙叫啥?","answer":"黄金赫拉克勒斯"}, 94 | {"title":"关于特摄假面骑士中骑士的安全区是下列那个地点?","answer":"水域"}, 95 | {"title":"以下哪位导演没有参与异形系列?","answer":"昆汀·塔伦蒂诺"}, 96 | {"title":"被钉X型十字架殉道的是?","answer":"圣安德烈"}, 97 | {"title":"该场景出现时真白说的第一句话是什么?","answer":"呐,你想成为什么颜色?"}, 98 | {"title":"提起法国剧作家罗斯丹的代表作《西哈诺》,你会想到以下哪部galgame?","answer":"美好的每一天"}, 99 | {"title":"下列歌手不是隶属于Sony音乐旗下的是","answer":"春奈露娜"}, 100 | {"title":"2017年7月20日发售的扶她百合游戏名为?","answer":"戦国の黒百合~ふたなり姫忍ぶ少女達~"}, 101 | {"title":"被称为西域战神的是?","answer":"阿古柏"}, 102 | {"title":"下面哪条 SQL 语句用于在数据库中插入新的数据?","answer":"INSERT NEW"}, 103 | {"title":"星之卡比中,卡比吞下小怪之后会发生什么","answer":"变身"}, 104 | {"title":"超炮中被称为海澜之家和上升气流lv6的是?","answer":"佐天泪子"}, 105 | {"title":"“所谓的人类,是连短短的十分钟也等不起的!”出自?","answer":"AngleBeats!"}, 106 | {"title":"生天目仁美曾为四大百合女王中的哪个配音?","answer":"花园静马"}, 107 | {"title":"前前前世出自哪部电影?","answer":"你的名字"}, 108 | {"title":"世界上最ng恋爱是哪位脚本作家的作品?","answer":"丸户史明"}, 109 | {"title":"Teaching Feeling中最有名的动作是","answer":"摸头"}, 110 | {"title":"与今野绪雪所写的女校故事《玛利亚的凝望》所对应的男校故事名为?","answer":"释迦摩尼也凝望"}, 111 | {"title":"胃药魔女冈田磨里的成名作是什么?","answer":"真实之泪"}, 112 | {"title":"动画《奇幻贵公子》的主角涉谷一也,被女主称为Naru的原因为。","answer":"百合熊岚"}, 113 | {"title":"以下那部不是15年的动漫四大名著?","answer":"自恋"}, 114 | {"title":"游戏《白色相簿》中,绪方理奈的职业是?","answer":"人气偶像"}, 115 | {"title":"欧派星人是哪位画师?","answer":"西又葵"}, 116 | {"title":"C3-魔方少女中的女主角是谁?","answer":"菲娅"}, 117 | {"title":"妖精森林的小不点第八集中,什么使两派人和好?","answer":"蜂蜜酒"}, 118 | {"title":"动画《绝园的暴风雨》里,不破爱花经常爱引用____的句子。","answer":"哈姆雷特"}, 119 | {"title":"下面哪个与游戏主题无关?","answer":"ReLIFE"}, 120 | {"title":"Gamers!第八集中,出现的key社作品为?","answer":"CLANNAD"}, 121 | {"title":"“エル・プサイ・コングルゥ!”出自?","answer":"Steins;Gate"}, 122 | {"title":"以下那作不是KEY社作品?","answer":"《Clover Day’s》"}, 123 | {"title":"《近月少女的礼仪》中人物的名字与以下哪个有关?","answer":"寺庙"}, 124 | {"title":"下面的四位中的那位是其中最漂亮的女主?","answer":"立华奏"}, 125 | {"title":"斯特拉的魔法中,SNS部之前的作品叫什么?","answer":"TEARMENT TEARSTAR"}, 126 | {"title":"小邪神飞踢!第二季中,出现的新人物名字叫什么?","answer":"僵僵"}, 127 | {"title":"学园长花火喜欢____。","answer":"巨乳"}, 128 | {"title":"在小说マリア様がみてる中出现的,小笠原祥子的胸围是?","answer":"65D"}, 129 | {"title":"约会大作战小说第21卷结束时中谁已经死了?","answer":"冰芽川四糸乃"}, 130 | ] 131 | 132 | resp = se.get(url).text 133 | print(resp) 134 | j = json.loads(resp) 135 | 136 | data = [] 137 | for k,i in enumerate(j): 138 | d = {} 139 | # print(i["code"]) 140 | # print(i["title"]) 141 | # print(i["options"]) 142 | for qa in doc: 143 | # print(i["title"],len(i["title"])) 144 | # print(qa["title"],len(qa["title"])) 145 | question = i["title"] 146 | question = i["title"].replace("?","") 147 | if question in qa["title"]: 148 | d["answer"] = qa["answer"] 149 | # print(d["answer"]) 150 | break 151 | else: 152 | print(question) 153 | print("\n当前第{}题不在题库中...".format(k+1)) 154 | print("题目为: ",i["title"]) 155 | print("选项为: ",i["options"]) 156 | print("第一项输入1,第二项输入2,第三项输入3,第四项输入4") 157 | num = input("选择答案项: ") 158 | if num == "": 159 | d["answer"] = random.choice(i["options"]) 160 | else: 161 | n = int(num)-1 162 | d["answer"] = i["options"][n] 163 | d["answer"] = i["options"][n] 164 | 165 | d["code"] = i["code"] 166 | data.append(d) 167 | # break 168 | 169 | p_resp = se.post(url,headers=headers,json=data).text 170 | res = json.loads(p_resp) 171 | print("考试分数为: ",res["score"]) 172 | print("获得邀请码: ",res["invitecode"]) 173 | # print(p_resp.text) -------------------------------------------------------------------------------- /0x02-花火学园邀请码脚本/img/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WriteCode-ChangeWorld/Tools/096dd8af8b9d1f501187ce62ab38e6be07f1518d/0x02-花火学园邀请码脚本/img/1.png -------------------------------------------------------------------------------- /0x02-花火学园邀请码脚本/img/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WriteCode-ChangeWorld/Tools/096dd8af8b9d1f501187ce62ab38e6be07f1518d/0x02-花火学园邀请码脚本/img/2.png -------------------------------------------------------------------------------- /0x02-花火学园邀请码脚本/requirement.txt: -------------------------------------------------------------------------------- 1 | requests -------------------------------------------------------------------------------- /0x03-iwara下载脚本/Readme.md: -------------------------------------------------------------------------------- 1 | ## Iwara下载脚本 V1.0.5 2 | 3 | --- 4 | 5 | ## 功能 6 | 7 | - [x] 支持下载`iwara`用户公开投稿的所有视频,视频文件下载存放在`./down/用户名`或`ROOT_DIR/用户名`文件夹下 8 | - [x] 支持自定义下载路径 (`ROOT_DIR=your path`) 9 | - [x] 支持单个`iwara`视频链接下载,视频文件下载存放在`./down`或`ROOT_DIR`文件夹下 10 | - [x] **添加视频完整性校验机制** (校验失败会进行重试、会删除校验失败的视频)——22.3.11 11 | - [x] **添加重试机制** (默认3次,`RETRY_COUNT=3`),支持重试网络错误或校验失败的视频——22.3.11 12 | - [x] 支持添加`cookie`下载`private`视频 13 | - [x] 多线程下载(默认8线程,`THREAD_NUM=8`) 14 | - [x] 日志记录功能,支持`debug`模式打印更多`log` (`DEBUG=True`) 15 | 16 | 17 | 18 | ## 使用方法 19 | 20 | ### 1、ROOT_DIR 21 | 22 | ```python 23 | # 填写下载目录 24 | ROOT_DIR = r"your root dir" 25 | 26 | # 若需要下载private视频,可自行填入账号cookie 27 | # 实验性 28 | cookie = "your cookie" 29 | ``` 30 | 31 | 32 | 33 | ### 2、下载依赖并启动 34 | 35 | ``` 36 | pip install -r requirement.txt 37 | python iwara.py 38 | ``` 39 | 40 | 41 | 42 | ## 运行截图 43 | 44 | ![](./img/1.png) 45 | 46 | ![](./img/2.png) 47 | 48 | ![](./img/3.png) -------------------------------------------------------------------------------- /0x03-iwara下载脚本/img/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WriteCode-ChangeWorld/Tools/096dd8af8b9d1f501187ce62ab38e6be07f1518d/0x03-iwara下载脚本/img/1.png -------------------------------------------------------------------------------- /0x03-iwara下载脚本/img/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WriteCode-ChangeWorld/Tools/096dd8af8b9d1f501187ce62ab38e6be07f1518d/0x03-iwara下载脚本/img/2.png -------------------------------------------------------------------------------- /0x03-iwara下载脚本/img/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WriteCode-ChangeWorld/Tools/096dd8af8b9d1f501187ce62ab38e6be07f1518d/0x03-iwara下载脚本/img/3.png -------------------------------------------------------------------------------- /0x03-iwara下载脚本/iwara.py: -------------------------------------------------------------------------------- 1 | import os 2 | import re 3 | import sys 4 | import json 5 | import time 6 | import glob 7 | import requests 8 | from lxml import etree 9 | from loguru import logger 10 | from urllib.parse import unquote 11 | # 强制取消警告 12 | from requests.packages.urllib3.exceptions import InsecureRequestWarning 13 | requests.packages.urllib3.disable_warnings(InsecureRequestWarning) 14 | 15 | 16 | from thread_pool import * 17 | 18 | 19 | # ======================= CONFIG =================== # 20 | # Version 21 | VERSION = "V1.0.8" 22 | # 如果需要下载private视频,则将自己的cookie填入此处 23 | cookie = "" 24 | # 设置为True 开启DEBUG模式 25 | DEBUG = False 26 | # 自定义下载路径 27 | ROOT_DIR = r"" 28 | # thread num 29 | THREAD_NUM = 8 30 | 31 | # log config 32 | if DEBUG: 33 | level = "DEBUG" 34 | else: 35 | level = "INFO" 36 | 37 | # remove default handler 38 | logger.remove() 39 | # 控制台输出 40 | logger.add( 41 | sys.stderr, 42 | level=level 43 | ) 44 | log_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "log") 45 | # 日志写入 46 | logger.add( 47 | os.path.join(log_path, "{time}.log"), 48 | encoding="utf-8", 49 | rotation="00:00", 50 | enqueue=True, 51 | level=level 52 | ) 53 | logger.add( 54 | os.path.join(log_path, "[ERROR video] {time}.log"), 55 | encoding="utf-8", 56 | rotation="00:00", 57 | enqueue=True, 58 | level="ERROR" 59 | ) 60 | # ======================= CONFIG =================== # 61 | 62 | 63 | # 重试视频列表 - 视频完整性校验失败 64 | # [{"args":args, "count":count},{}...] 65 | RETRY_COUNT = 3 66 | ERR_TASK_LIST = [] 67 | 68 | 69 | def check_video(resp, path)->bool: 70 | """ 71 | :params resp: 响应体 72 | :params path: 本地视频 73 | """ 74 | local_size = int(os.path.getsize(path)) 75 | resp_size = int(resp.headers["content-length"]) 76 | logger.debug(f"{local_size} {resp_size}") 77 | if local_size != resp_size: 78 | return False 79 | else: 80 | return True 81 | 82 | def byte2size(value): 83 | value = int(value) 84 | units = ["B", "KB", "MB", "GB", "TB", "PB"] 85 | size = 1024.0 86 | for i in range(len(units)): 87 | if (value/size) < 1: 88 | return "%.2f%s" % (value, units[i]) 89 | value = value/size 90 | 91 | def dealwith_title(title): 92 | title = re.sub('[\/:*?"<>|]', '_', title) 93 | title = title.replace("&", "&").replace("\u3000", "").\ 94 | replace(".", "_") 95 | return title 96 | 97 | def check_errTask_exists(data, err_task_list): 98 | for _ in err_task_list: 99 | if data == _["args"]: 100 | return True 101 | return False 102 | 103 | 104 | class IwaraDownloader: 105 | """ 106 | 目前支持 107 | 1、iwara用户的所有视频下载,存储在单个的<用户名称>文件夹中 108 | 2、单个iwara视频下载,存储在down文件夹下 109 | 110 | iwara地址:https://ecchi.iwara.tv/videos/v2km9s5npeterzlad 111 | 对应api接口为:https://ecchi.iwara.tv/api/video/v2km9s5npeterzlad 112 | """ 113 | def __init__(self,links): 114 | self.iwara_host = "https://ecchi.iwara.tv" 115 | 116 | # 拼接videos页面 117 | # https://ecchi.iwara.tv/users/Forget%20Skyrim./videos?page=3 118 | self.host = "https://ecchi.iwara.tv{}?language=zh&page={}" 119 | 120 | # 视频信息接口 121 | self.api_url = "https://ecchi.iwara.tv/api/video/{}" 122 | # conver_link:用来判断跳出的页码 123 | self.page_num = 0 124 | 125 | # 页面请求头 126 | self.page_headers = { 127 | "user-agent":"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.25 Safari/537.36 Core/1.70.3741.400 QQBrowser/10.5.3863.400", 128 | "referer":"https://ecchi.iwara.tv/", 129 | "accept-language": "zh-CN,zh;q=0.9" 130 | } 131 | # get_data请求头 132 | self.headers = { 133 | "host":"ecchi.iwara.tv", 134 | "user-agent":"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.25 Safari/537.36 Core/1.70.3741.400 QQBrowser/10.5.3863.400", 135 | } 136 | # 添加用户自定义cookie,主要用于private视频获取 137 | if cookie: 138 | self.headers["cookie"] = cookie 139 | 140 | # 需要下载的iwara链接列表 141 | if links == []: 142 | logger.error("Iwara链接列表为空!") 143 | exit() 144 | else: 145 | # 去重 146 | self.original_links = list(set(links)) 147 | logger.info(f"输入链接数量 - {len(self.original_links)}") 148 | logger.debug(f"输入链接 - {self.original_links}") 149 | 150 | # 初始化下载主目录 151 | self.root_path = os.path.abspath(os.path.dirname(__file__)) if not ROOT_DIR else ROOT_DIR 152 | if not os.path.exists(self.root_path):os.mkdir(self.root_path) 153 | 154 | self.error_code_list = { 155 | "ErrorVideoUrl": "无效的视频链接", 156 | "ErrorUserUrl": "错误的用户主页链接", 157 | "EmptyVideoLinks": "该用户无视频", 158 | "PrivateVideoUrl": "该视频为private视频 或 视频无法播放下载", 159 | "JsonDecodeError": "解析json数据出错", 160 | "UnknownError": "不明原因导致的失败", 161 | "NetWorkTimeoutError": "网络或代理超时" 162 | } 163 | 164 | def get_resp(self,url, 165 | params=None, 166 | headers=None, 167 | retry_num=3, 168 | stream=False, 169 | ): 170 | """ 171 | 一个简单的网络请求函数 172 | """ 173 | if headers == None: 174 | headers = self.page_headers 175 | try: 176 | resp = requests.get(url,params=params, 177 | headers=headers, 178 | timeout=20, 179 | stream=stream, 180 | verify=False 181 | ) 182 | except Exception as e: 183 | if retry_num >= 3: 184 | logger.info(f"网络错误,正在重试 - {url}") 185 | return self.get_resp(url,params=params,headers=headers,retry_num=retry_num-1,stream=stream) 186 | else: 187 | logger.warning(f"超出重试次数 - {url}") 188 | logger.warning(f"Exception - {e}") 189 | return None 190 | else: 191 | time.sleep(1) 192 | return resp 193 | 194 | def get_title(self,link): 195 | """提供iwara视频链接,获取标题""" 196 | html = self.get_resp(link) 197 | if type(html) == type(None): 198 | return "NetWorkTimeoutError" 199 | response = html.text.replace("\u3000","") 200 | r = re.findall("(.*?)", response)[0].split("| Iwara")[0] 201 | title = dealwith_title(r) 202 | return title 203 | 204 | def get_data(self,link): 205 | """ 206 | 获取视频接口信息,返回原始视频链接和动态host 207 | :params link: iwara video url, https://ecchi.iwara.tv/videos/9jqbjse8qhzmo8ja 208 | :return: [source_url,host] or err str 209 | """ 210 | # url - https://ecchi.iwara.tv/api/video/4kbwafpkz0tyv3k4m 211 | url = self.api_url.format(link.split("/")[-1]) 212 | logger.debug(f" - {url}") 213 | resp = self.get_resp(url,headers=self.headers) 214 | 215 | if resp.text == '[]': 216 | # private视频 217 | return "PrivateVideoUrl" 218 | else: 219 | try: 220 | json_data = json.loads(resp.text) 221 | except json.decoder.JSONDecodeError as e: 222 | logger.warning(f"JsonDecodeError Exception - {e}") 223 | return "JsonDecodeError" 224 | except Exception as e: 225 | logger.warning(f"UnknownError Exception - {e}") 226 | return "UnknownError" 227 | else: 228 | source_url = f'https:{[r["uri"] for r in json_data if r["resolution"] == "Source"][0]}' 229 | host = source_url.split("file.php")[0] 230 | return [source_url,host] 231 | 232 | def get_links(self,link,pageNum): 233 | """ 234 | https://ecchi.iwara.tv/users/%E5%92%95%E5%98%BF%E5%98%BF/videos?page=1 235 | https://ecchi.iwara.tv/users/forget-skyrim 236 | 获取当前页面的iwara链接和页数 237 | 视频不超过16个的用户,无videos页面 238 | """ 239 | href_list,title_list = [],[] 240 | resp = self.get_resp(link,headers=self.page_headers).text 241 | home_obj = etree.HTML(resp) 242 | home_expression = '//div[@id="block-views-videos-block-2"]//div[@class="more-link"]//a/@href' 243 | 244 | # 无效作者主页/无效视频 245 | if "ユーザーリスト | Iwara" in resp: 246 | return ["ErrorUserUrl"],0 247 | 248 | # 无videos页面 --> 获取用户主页视频 249 | if home_obj.xpath(home_expression) == []: 250 | # /videos/83wvbuz4yfl7bn3l 251 | href_expression = '//div[@id="block-views-videos-block-2"]//div[contains(@class,"views-responsive-grid")]//h3/a/@href' 252 | title_expression = '//div[@id="block-views-videos-block-2"]//div[contains(@class,"views-responsive-grid")]//h3/a/text()' 253 | if home_obj.xpath(href_expression) != []: 254 | href_list = [f"{self.iwara_host}{r}" for r in home_obj.xpath(href_expression)] 255 | logger.info(f"User Have {len(href_list)} Videos. Not Video Page") 256 | # 转义html字符 257 | title_list = [self.get_title(href) for href in href_list] 258 | 259 | # 补充无Video Page的业务跳出条件 260 | total_pageNum = -1 261 | # 用户无视频 262 | else: 263 | logger.info("User Not Have Video") 264 | return [],0 265 | # 有videos页面 --> 获取用户videos页面链接 266 | else: 267 | useVideoUrl = home_obj.xpath(home_expression)[0].split("?")[0] 268 | page_link = self.host.format(useVideoUrl,pageNum) 269 | 270 | obj = etree.HTML(self.get_resp(page_link,headers=self.page_headers).text) 271 | 272 | href_list = ["{}{}".format(self.iwara_host,r) for r in obj.xpath("//h3[@class='title']/a/@href")] 273 | # logger.info(f"Page Have {len(href_list)} Videos") 274 | # 转义html字符 275 | title_list = [dealwith_title(r) for r in obj.xpath(""".//div[@class='field-item even']/a/img/@title""")] 276 | # 页数 277 | try: 278 | total_pageNum = int(obj.xpath("//li[@class='pager-last last']//@href")[0].split("page=")[-1]) 279 | except: 280 | # logger.warning(f"User Have Video Page. LastOne - {page_link}") 281 | logger.info(f": {int(self.page_num)+1}/{int(self.page_num)+1} : {len(href_list)}") 282 | # 最后一页 283 | total_pageNum = -1 284 | else: 285 | logger.info(f": {int(pageNum)+1}/{int(total_pageNum)+1} : {len(href_list)}") 286 | 287 | 288 | 289 | res = [] 290 | for h,t in zip(href_list,title_list): 291 | res.append({"link":h,"title":f"{t}--{h.split('videos/')[-1].split('?')[0]}"}) 292 | 293 | logger.debug("Exit Function") 294 | return res,total_pageNum 295 | 296 | def check_folder(self,link): 297 | """ 298 | https://ecchi.iwara.tv/users/%E8%B4%BE%E5%94%AF%E2%84%A1 299 | :params link: 原始链接 300 | :return :创建作者目录并返回目录 301 | """ 302 | user = link.split("users/")[-1] 303 | user = unquote(user,'utf-8') 304 | user_path = os.path.join(self.root_path,user) 305 | if not os.path.exists(user_path): 306 | os.mkdir(user_path) 307 | return user_path 308 | 309 | def conver_link(self,link): 310 | """ 311 | :params link: 作者主页链接 / 视频链接 312 | :return :原始链接 or 包含作者视频链接的列表 313 | 314 | 链接类型 315 | 1、作者主页 https://ecchi.iwara.tv/users/xxxx 316 | 317 | 更改获取videos页面的逻辑,改为从作者主页获取 318 | + 作者主页: https://ecchi.iwara.tv/users/forget-skyrim 319 | + videos页面会发生变化: https://ecchi.iwara.tv/users/Forget%20Skyrim./videos 320 | 321 | 2、单个视频链接 https://ecchi.iwara.tv/videos/xxxx 322 | """ 323 | user_video_links = [] 324 | if "/users" in link: 325 | logger.debug(f":{link} type:User") 326 | pageNum = 0 327 | while True: 328 | page_video_links,total_pageNum = self.get_links(link,pageNum) 329 | user_video_links.extend(page_video_links) 330 | pageNum += 1 331 | self.page_num = total_pageNum 332 | # 业务跳出条件 333 | if self.page_num == -1: 334 | break 335 | 336 | # 无效用户/视频链接 用户无视频 337 | if user_video_links == [] and \ 338 | page_video_links == [] and \ 339 | total_pageNum == 0: 340 | break 341 | 342 | # 用户无视频 343 | if user_video_links == []: 344 | return "EmptyVideoLinks" 345 | 346 | # 无效作者主页/视频链接 347 | if user_video_links[0] == "ErrorUserUrl": 348 | return "ErrorUserUrl" 349 | 350 | # 创建作者目录 351 | user_path = self.check_folder(link) 352 | for video in user_video_links: 353 | video["path"] = user_path 354 | # [{link, title, path}, {...}, ...] 355 | return user_video_links 356 | 357 | logger.debug(f":{link} type:Video") 358 | # 视频链接 359 | title = self.get_title(link) 360 | 361 | # 无效的视频链接 362 | if title == "Iwara": 363 | return "ErrorVideoUrl" 364 | # err code 365 | elif title in self.error_code_list.keys(): 366 | return title 367 | 368 | return [{"link":link, "title":f"{title}--{link.split('videos/')[-1].split('?')[0]}", "path":self.root_path}] 369 | 370 | def download(self,args,source_url,file_path): 371 | """ 372 | 下载原始视频 373 | """ 374 | response = self.get_resp(source_url,headers=self.page_headers,stream=True) 375 | logger.info(f"视频标题: {args[0]['title']} - 大小: {byte2size(response.headers['content-length'])} 正在下载") 376 | 377 | # content_size = int(response.headers['content-length']) # 获得文件大小(字节) 378 | # data_count = 0 # 计数 379 | chunk_size = 1024 # 每次最大请求字节 380 | with open(file_path, "wb") as file: 381 | for data in response.iter_content(chunk_size=chunk_size): 382 | file.write(data) 383 | return response 384 | 385 | @logger.catch 386 | def iwara_process(self, *args): 387 | """ 388 | 子线程任务函数 389 | """ 390 | link, title, path = args[0]["link"], args[0]["title"], args[0]["path"] 391 | file_path = f"{os.path.join(path, title)}.mp4" 392 | logger.debug(f" - {file_path}") 393 | logger.debug(f" - {link}") 394 | 395 | # 检测本地文件 396 | isExists = False 397 | video_id = title.split("--")[-1] 398 | path_expression = os.path.join(path, f"**--{video_id}.mp4") 399 | 400 | for fn in glob.glob(path_expression): 401 | isExists = True 402 | break 403 | 404 | if isExists and os.path.getsize(glob.glob(path_expression)[0]) > 100000: 405 | logger.success(f"视频标题: {title} 已存在") 406 | return 407 | 408 | try: 409 | data = self.get_data(link) 410 | # err code 411 | if type(data) == type("") and data in self.error_code_list.keys(): 412 | err_code = self.error_code_list.get(data,'NotFoundErrorCode') 413 | logger.info(f"视频标题: {title} | err_code: {err_code}") 414 | return 415 | else: 416 | source_url,host = data[0],data[1] 417 | except Exception as e: 418 | logger.warning(f"Exception - {e}") 419 | return 420 | else: 421 | try: 422 | response = self.download(args,source_url,file_path) 423 | except Exception as e: 424 | logger.warning(f"视频标题: {title} 视频下载失败,将在其他任务完成后进行重试.") 425 | logger.error(f"""错误视频链接: {link} - 视频标题: {title} - """\ 426 | f"""服务器:{byte2size(response.headers['content-length'])} - 本地:{byte2size(os.path.getsize(file_path))}""") 427 | if not check_errTask_exists(args, ERR_TASK_LIST): 428 | ERR_TASK_LIST.append({"args": args, "count": RETRY_COUNT}) 429 | # os.remove(file_path) 430 | else: 431 | # 3.9 视频完整性校验 432 | if not check_video(response, file_path): 433 | logger.warning(f"视频标题: {title} 视频完整性校验失败,将在其他任务完成后进行重试.") 434 | logger.error(f"""错误视频链接: {link} - 视频标题: {title} - """\ 435 | f"""服务器:{byte2size(response.headers['content-length'])} - 本地:{byte2size(os.path.getsize(file_path))}""") 436 | if not check_errTask_exists(args, ERR_TASK_LIST): 437 | ERR_TASK_LIST.append({"args": args, "count": RETRY_COUNT}) 438 | os.remove(file_path) 439 | else: 440 | logger.success(f"视频标题: {title} - {byte2size(response.headers['content-length'])}下载成功 让我歇歇,冲不动了~") 441 | 442 | # 判断/移除重试任务 443 | logger.debug(f"ERR_TASK_LIST 错误重试任务剩余: {len(ERR_TASK_LIST)}个") 444 | logger.debug(ERR_TASK_LIST) 445 | for _ in ERR_TASK_LIST[::]: 446 | if args == _["args"]: 447 | if _["count"] == 0: 448 | ERR_TASK_LIST.remove(_) 449 | logger.warning(f"视频标题: {title} 下载失败,无重试次数. 请参考log文件夹下的ERROR日志~") 450 | else: 451 | logger.warning(f"视频标题: {title} 剩余重试次数: {_['count']}次") 452 | _["count"] -= 1 453 | return self.iwara_process(*args) 454 | break 455 | 456 | time.sleep(0.5) 457 | 458 | def main(self): 459 | """ 460 | 一、单个iwara视频 461 | 1.无限制 https://ecchi.iwara.tv/videos/4kbwafpkz0tyv3k4m 462 | 2.private https://ecchi.iwara.tv/videos/wdz0ofvm9buqwapjb 463 | 3.无效视频 https://ecchi.iwara.tv/videos/xxxxxxx 464 | 465 | 二、作者主页 466 | 1.无private 467 | 1.小于等于16个视频 https://ecchi.iwara.tv/users/shikou 468 | 2.大于16个视频 1页无页码 https://ecchi.iwara.tv/users/forget-skyrim 469 | 2页以上 https://ecchi.iwara.tv/users/shirakamisan 470 | # 存在无法播放的视频,按private视频处理 471 | 2.有private 472 | 1.小于等于16个视频 暂时没找到,按1.1处理 473 | 2.大于16个视频 https://ecchi.iwara.tv/users/贾唯℡ 474 | 475 | 3.无视频 https://ecchi.iwara.tv/users/lilan0538 476 | 4.无效作者主页 https://ecchi.iwara.tv/users/测试 477 | """ 478 | pool = ThreadPool(THREAD_NUM) 479 | # 下载 480 | try: 481 | for link in self.original_links: 482 | # i站相关链接下载 483 | if link.startswith("https://ecchi.iwara.tv/"): 484 | logger.debug(f" - {link}") 485 | link_list = self.conver_link(link) 486 | 487 | # err code 488 | if type(link_list) == type("link_list") and link_list in self.error_code_list.keys(): 489 | err_code = self.error_code_list.get(link_list,"NotFoundErrorCode") 490 | logger.info(f"【{link}】 {err_code}") 491 | continue 492 | 493 | # thread working 494 | if link_list != []: 495 | logger.info(f" - {len(link_list)}") 496 | logger.debug(f"link_list - {link_list}") 497 | 498 | for l in link_list: 499 | pool.put(self.iwara_process, (l, ), callback) 500 | except Exception as e: 501 | logger.warning(f"Exception - {e}") 502 | # 错误重试 503 | else: 504 | logger.debug(ERR_TASK_LIST) 505 | for l in ERR_TASK_LIST: 506 | pool.put(self.iwara_process, (l, ), callback) 507 | finally: 508 | pool.close() 509 | 510 | 511 | if __name__ == '__main__': 512 | logger.success(f"=== 欢迎使用< iwara下载脚本 {VERSION} > ===") 513 | logger.success(f"Github地址: https://github.com/WriteCode-ChangeWorld/Tools 欢迎Star~") 514 | 515 | logger.info("下载作者视频则输入作者主页链接...") 516 | logger.info("如: https://ecchi.iwara.tv/users/qishi\n") 517 | logger.info("下载单个视频则输入视频链接...") 518 | logger.info("如: https://ecchi.iwara.tv/videos/4kbwafpkz0tyv3k4m\n") 519 | logger.info("输入完成后,请输入'go'以开始下载...") 520 | # https://ecchi.iwara.tv/users/%E8%B4%BE%E5%94%AF%E2%84%A1 含private视频 521 | # https://ecchi.iwara.tv/users/qishi 522 | 523 | input_links = [] 524 | while True: 525 | input_iwara_link = input("现在输入Iwara链接: ") 526 | if input_iwara_link == "": 527 | pass 528 | elif input_iwara_link == "go": 529 | logger.info("开冲!~ (*^▽^*)") 530 | break 531 | else: 532 | input_links.append(input_iwara_link) 533 | 534 | logger.info("现在开始获取链接,请稍后...") 535 | IDLoader = IwaraDownloader(input_links) 536 | IDLoader.main() -------------------------------------------------------------------------------- /0x03-iwara下载脚本/requirement.txt: -------------------------------------------------------------------------------- 1 | requests 2 | lxml 3 | loguru -------------------------------------------------------------------------------- /0x03-iwara下载脚本/thread_pool.py: -------------------------------------------------------------------------------- 1 | # coding=utf8 2 | 3 | """ 4 | 一个基于thread和queue的线程池, 5 | 任务为队列元素,动态创建线程并重复利用. 6 | 通过close和terminate关闭线程池. 7 | """ 8 | 9 | import queue 10 | import threading 11 | import contextlib 12 | import time 13 | 14 | # 创建空对象,用于停止线程 15 | StopEvent = object() 16 | 17 | 18 | def callback(status, result): 19 | """ 20 | 根据需要进行的回调函数,默认不执行。 21 | :param status: action函数的执行状态 22 | :param result: action函数的返回值 23 | :return: 24 | """ 25 | pass 26 | 27 | 28 | def action(thread_name, arg): 29 | """ 30 | 真实的任务定义在这个函数里 31 | :param thread_name: 执行该方法的线程名 32 | :param arg: 该函数需要的参数 33 | :return: 34 | """ 35 | # 模拟执行 36 | time.sleep(0.1) 37 | print("第%s个任务调用了线程 %s,并打印了这条信息!" % (arg+1, thread_name)) 38 | 39 | 40 | class ThreadPool: 41 | 42 | def __init__(self, max_num, max_task_num=None): 43 | """ 44 | 初始化线程池 45 | :param max_num: 线程池最大线程数量 46 | :param max_task_num: 任务队列长度 47 | """ 48 | # 如果提供了最大任务数的参数,则将队列的最大元素个数设置为这个值。 49 | max_task_num = 20 50 | if max_task_num: 51 | self.q = queue.Queue(max_task_num) 52 | # 默认队列可接受无限多个的任务 53 | else: 54 | self.q = queue.Queue() 55 | # print("创建好队列了") 56 | # 设置线程池最多可实例化的线程数 57 | self.max_num = max_num 58 | # 任务取消标识 59 | self.cancel = False 60 | # 任务中断标识 61 | self.terminal = False 62 | # 已实例化的线程列表 63 | self.generate_list = [] 64 | # 处于空闲状态的线程列表 65 | self.free_list = [] 66 | 67 | def put(self, func, args, callback=None): 68 | """ 69 | 往任务队列里放入一个任务 70 | :param func: 任务函数 71 | :param args: 任务函数所需参数 72 | :param callback: 任务执行失败或成功后执行的回调函数,回调函数有两个参数 73 | 1、任务函数执行状态;2、任务函数返回值(默认为None,即:不执行回调函数) 74 | :return: 如果线程池已经终止,则返回True否则None 75 | """ 76 | # 先判断标识,看看任务是否取消了 77 | if self.cancel: 78 | return 79 | # 如果没有空闲的线程,并且已创建的线程的数量小于预定义的最大线程数,则创建新线程。 80 | if len(self.free_list) == 0 and len(self.generate_list) < self.max_num: 81 | self.generate_thread() 82 | # 构造任务参数元组,分别是调用的函数,该函数的参数,回调函数。 83 | w = (func, args, callback,) 84 | # 将任务放入队列 85 | self.q.put(w) 86 | 87 | def generate_thread(self): 88 | """ 89 | 创建一个线程 90 | """ 91 | # 每个线程都执行call方法 92 | t = threading.Thread(target=self.call) 93 | t.start() 94 | 95 | def call(self): 96 | """ 97 | 循环去获取任务函数并执行任务函数。在正常情况下,每个线程都保存生存状态,直到获取线程终止的flag。 98 | """ 99 | # 获取当前线程的名字 100 | current_thread = threading.currentThread().getName() 101 | # 将当前线程的名字加入已实例化的线程列表中 102 | self.generate_list.append(current_thread) 103 | # 从任务队列中获取一个任务 104 | event = self.q.get() 105 | # 让获取的任务不是终止线程的标识对象时 106 | while event != StopEvent: 107 | # 解析任务中封装的三个参数 108 | func, arguments, callback = event 109 | # 抓取异常,防止线程因为异常退出 110 | try: 111 | # 正常执行任务函数 112 | result = func(*arguments) 113 | # result = func(current_thread, *arguments) 114 | success = True 115 | except Exception as e: 116 | # 当任务执行过程中弹出异常 117 | result = None 118 | success = False 119 | # 如果有指定的回调函数 120 | if callback is not None: 121 | # 执行回调函数,并抓取异常 122 | try: 123 | callback(success, result) 124 | except Exception as e: 125 | pass 126 | # 当某个线程正常执行完一个任务时,先执行worker_state方法 127 | with self.worker_state(self.free_list, current_thread): 128 | # 如果强制关闭线程的flag开启,则传入一个StopEvent元素 129 | if self.terminal: 130 | event = StopEvent 131 | # 否则获取一个正常的任务,并回调worker_state方法的yield语句 132 | else: 133 | # 从这里开始又是一个正常的任务循环 134 | event = self.q.get() 135 | else: 136 | # 一旦发现任务是个终止线程的标识元素,将线程从已创建线程列表中删除 137 | self.generate_list.remove(current_thread) 138 | 139 | def close(self): 140 | """ 141 | 执行完所有的任务后,让所有线程都停止的方法 142 | """ 143 | # 设置flag 144 | self.cancel = True 145 | # 计算已创建线程列表中线程的个数, 146 | # 然后往任务队列里推送相同数量的终止线程的标识元素 147 | full_size = len(self.generate_list) 148 | while full_size: 149 | self.q.put(StopEvent) 150 | full_size -= 1 151 | 152 | def terminate(self): 153 | """ 154 | 在任务执行过程中,终止线程,提前退出。 155 | """ 156 | self.terminal = True 157 | # 强制性的停止线程 158 | while self.generate_list: 159 | self.q.put(StopEvent) 160 | 161 | # 该装饰器用于上下文管理 162 | @contextlib.contextmanager 163 | def worker_state(self, state_list, worker_thread): 164 | """ 165 | 用于记录空闲的线程,或从空闲列表中取出线程处理任务 166 | """ 167 | # 将当前线程,添加到空闲线程列表中 168 | state_list.append(worker_thread) 169 | # 捕获异常 170 | try: 171 | # 在此等待 172 | yield 173 | finally: 174 | # 将线程从空闲列表中移除 175 | state_list.remove(worker_thread) 176 | 177 | """ 178 | 179 | # 调用方式 180 | if __name__ == '__main__': 181 | # 创建一个最多包含5个线程的线程池 182 | # pool = ThreadPool(5) 183 | 184 | # 创建100个任务,让线程池进行处理 185 | # for i in range(100): 186 | # pool.put(action, (i,), callback) 187 | 188 | # 等待一定时间,让线程执行任务 189 | # time.sleep(3) 190 | 191 | print("-" * 50) 192 | while True: 193 | print("\033[32;0m任务停止之前线程池中有%s个线程,空闲的线程有%s个!\033[0m" 194 | % (len(pool.generate_list), len(pool.free_list))) 195 | 196 | if len(pool.free_list) == pool.max_num and len(pool.generate_list) == 5: 197 | # 正常关闭线程池 198 | pool.close() 199 | print("任务执行完毕,正常退出!") 200 | break 201 | 202 | time.sleep(1) 203 | # 强制关闭线程池 204 | # pool.terminate() 205 | # print("强制停止任务!") 206 | 207 | """ -------------------------------------------------------------------------------- /0x04-mzitu爬虫/Readme.md: -------------------------------------------------------------------------------- 1 | ## **mzitu下载脚本** 2 | 3 | --- 4 | 5 | 这几天偶然看了下妹子图站点,看到`all`和`old`页面已经404了(大多数都是基于这两个页面进行爬取) 6 | 7 | 故改动一下之前的版本,便有了**mzitu下载脚本** 8 | 9 | --- 10 | 11 | 功能: 12 | 13 | - [x] 下载妹子图全站图片 14 | - [x] 每次请求会延时0.1秒,以确保站点正常业务运行 15 | - [x] 已下载目录跳过机制 16 | 17 | 18 | 19 | 使用方法: 20 | 21 | 1、安装依赖 22 | 23 | ``` 24 | pip install -r requirement.txt 25 | ``` 26 | 27 | 2、修改`self.path`为你的下载目录 28 | 29 | ``` 30 | # 代码第20行 31 | # 将此处修改为你的存储地址 32 | self.path = r'D:\Tools\mzitu' 33 | ``` 34 | 35 | 3、运行 36 | 37 | ``` 38 | python mzitu.py 39 | ``` 40 | 41 | 中途停止可用`CTRL`+`C`大法进行中断,下次再运行继续下载即可 42 | 43 | 44 | 45 | 运行截图 46 | 47 | ![](./img/1.png) 48 | 49 | ![](./img/2.png) 50 | 51 | -------------------------------------------------------------------------------- /0x04-mzitu爬虫/img/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WriteCode-ChangeWorld/Tools/096dd8af8b9d1f501187ce62ab38e6be07f1518d/0x04-mzitu爬虫/img/1.png -------------------------------------------------------------------------------- /0x04-mzitu爬虫/img/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WriteCode-ChangeWorld/Tools/096dd8af8b9d1f501187ce62ab38e6be07f1518d/0x04-mzitu爬虫/img/2.png -------------------------------------------------------------------------------- /0x04-mzitu爬虫/mzitu.py: -------------------------------------------------------------------------------- 1 | import re 2 | import os 3 | import time 4 | import random 5 | import requests 6 | from lxml import etree 7 | 8 | from requests.packages.urllib3.exceptions import InsecureRequestWarning #强制取消警告 9 | requests.packages.urllib3.disable_warnings(InsecureRequestWarning) 10 | 11 | class mzitu(): 12 | def __init__(self): 13 | self.headers = { 14 | 'User-Agent': "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/535.24 (KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24", 15 | 'referer':'https://www.mzitu.com' 16 | } 17 | self.host = "https://www.mzitu.com" 18 | self.template = "https://www.mzitu.com/page/{}" 19 | # 修改self.path为你的存储地址 20 | self.path = r'D:\Tools\mzitu' 21 | self.folder_path = '' 22 | 23 | def max_num(self)->int: 24 | resp = self.request(self.host) 25 | resp_obj = etree.HTML(resp.text) 26 | resp_expression = """//div[@class='nav-links']//a/text()""" 27 | resp_result = resp_obj.xpath(resp_expression) 28 | if resp_result == []: 29 | return 0 30 | else: 31 | num = int(resp_result[-2]) 32 | return num 33 | 34 | def main(self): 35 | """ 36 | 主流程 37 | """ 38 | num = self.max_num() 39 | print("一共有{}页".format(num)) 40 | if num == 0: 41 | exit() 42 | 43 | for i in range(1,num+1): 44 | page_url = self.template.format(i) 45 | print(page_url) 46 | 47 | page_result = self.get_page_html(page_url) 48 | if page_result == []: 49 | continue 50 | 51 | for page_data in page_result: 52 | print("正在下载【{}】--{}".format(page_data["name"],page_data["href"])) 53 | # 创建套图文件夹 54 | self.folder_path = self.create_folder(page_data["name"]) 55 | # 获取套图的所有图片链接 56 | self.get_img_href(page_data["href"]) 57 | # break 58 | 59 | # break 60 | 61 | def get_page_html(self,page_url:str)->dict: 62 | """ 63 | 获取当前page页面,各个套图的名称及链接 64 | :param page_url: 页面url:https://www.mzitu.com/page/1 65 | :return: 当前页面的套图名称及链接 66 | """ 67 | html = self.request(page_url) 68 | html_obj = etree.HTML(html.text) 69 | name_expression = """//ul[@id='pins']//li/span/a/text()""" 70 | href_expression = """//ul[@id='pins']//li/span/a/@href""" 71 | 72 | name_list = html_obj.xpath(name_expression) 73 | href_list = html_obj.xpath(href_expression) 74 | 75 | result = [] 76 | for name,href in zip(name_list,href_list): 77 | result.append({"name":name,"href":href}) 78 | return result 79 | 80 | def get_img_href(self,url:str)->None: 81 | """ 82 | 图片页面获取多个图片地址,并下载原图 83 | :param url: 套图链接,https://www.mzitu.com/227785 84 | :return: 套图链接中所有图片的原图链接 85 | """ 86 | html = self.request(url) 87 | obj = etree.HTML(html.text) 88 | img_expression = """//div[@class="main-image"]//img/@src""" 89 | num_expression = """//div[@class='pagenavi']//a/span/text()""" 90 | 91 | num = int(obj.xpath(num_expression)[-2]) 92 | 93 | # 套图目录下有对应num张的图片则跳过该套图下载 94 | if len(os.listdir(self.folder_path)) == num: 95 | # 休眠 96 | time.sleep(0.1) 97 | return 98 | 99 | for i in range(1,num+1): 100 | # https://www.mzitu.com/227785/61 101 | img_url = "{}/{}".format(url,i) 102 | print(img_url) 103 | img_resp = self.request(img_url) 104 | img_obj = etree.HTML(img_resp.text) 105 | 106 | try: 107 | img_href = img_obj.xpath(img_expression)[0] 108 | except: 109 | img_href = "" 110 | img_name = img_href.rsplit("/",1)[-1] 111 | 112 | img_path = os.path.join(self.folder_path,img_name) 113 | # 不存在则下载 114 | if os.path.exists(img_path) == False: 115 | self.save_img(img_href,img_path) 116 | 117 | # 存在但字节数少于1000 118 | if os.path.exists(img_path) == True and os.path.getsize(img_path) < 1000: 119 | self.save_img(img_href,img_path) 120 | 121 | # 休眠 122 | time.sleep(0.1) 123 | 124 | def save_img(self,img_url:str,path:str)->None: 125 | """ 126 | 写入图片二进制数据 127 | 128 | :param img_url: 图片链接 129 | :param path: 图片存储路径 130 | :return: None 131 | """ 132 | img = self.request(img_url) 133 | with open(path, 'ab') as f: 134 | f.write(img.content) 135 | 136 | def create_folder(self,title:str)->str: 137 | """ 138 | 创建文件夹 139 | :param title: 文件夹名称: 140 | :return: 创建的文件夹路径 141 | """ 142 | title = re.sub(r'[\/:*?"<>|]','_',title) 143 | folder_path = os.path.join(self.path,title) 144 | isExists = os.path.exists(folder_path) 145 | if not isExists: 146 | print("已创建【{}】该文件夹".format(title)) 147 | os.makedirs(folder_path) 148 | return folder_path 149 | print("已存在【{}】该文件夹".format(title)) 150 | return folder_path 151 | 152 | def request(self,url:str,retry_num=5): 153 | try: 154 | response = requests.get(url, headers=self.headers,verify=False, timeout=10) 155 | response.encoding = "utf8" 156 | return response 157 | except Exception as e: 158 | if retry_num > 0: 159 | return self.request(url,retry_num-1) 160 | else: 161 | print('网络错误:{} {}'.format(url,e)) 162 | return None 163 | 164 | Mzitu = mzitu() 165 | Mzitu.main() -------------------------------------------------------------------------------- /0x04-mzitu爬虫/requirement.txt: -------------------------------------------------------------------------------- 1 | requests 2 | lxml -------------------------------------------------------------------------------- /0x05-绅士小站/Readme.md: -------------------------------------------------------------------------------- 1 | ## **绅士小站下载爬虫** v1.0.2——2021年3月2日 2 | 3 | --- 4 | 5 | 绅士仓库看到的一个[站点](https://ca.gca.tw),上面都是仓库内某个同学收集的里番。 6 | 7 | 点开就心动了,花了两个小时写完这个脚本就传上来了。 8 | 9 | --- 10 | 11 | 功能: 12 | 13 | - [x] 下载视频和视频封面(目前存在可能获取不到视频/视频封面的Bug)——**已解决** 14 | - [x] 动态下载进度条 15 | - [x] 网络请求自动重试 16 | - [x] 已下载视频/视频封面自动跳过(字节数大于1000) 17 | 18 | 19 | 20 | 使用方法: 21 | 22 | 1、安装依赖 23 | 24 | ``` 25 | pip install -r requirement.txt 26 | ``` 27 | 28 | 2、运行 29 | 30 | ``` 31 | python gca.py 32 | ``` 33 | 34 | 备注:如果有漏下载或当前文章无视频及视频封面(实际上有)的情况,再运行一次脚本补全即可。 35 | 36 | 37 | 38 | 运行截图 39 | 40 | ![](./img/1.jpg) 41 | 42 | ![](./img/2.png) 43 | 44 | ![](./img/3.png) 45 | 46 | -------------------------------------------------------------------------------- /0x05-绅士小站/gca.py: -------------------------------------------------------------------------------- 1 | import os 2 | import re 3 | import time 4 | import requests 5 | from lxml import etree 6 | from requests.packages.urllib3.exceptions import InsecureRequestWarning 7 | requests.packages.urllib3.disable_warnings(InsecureRequestWarning) 8 | 9 | default_headers = { 10 | "user-agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Safari/537.36", 11 | "referer":"https://ca.gca.tw", 12 | } 13 | 14 | def log_str(*args,end=None): 15 | for i in args: 16 | print('[{}] {}'.format(time.strftime("%Y-%m-%d %H:%M:%S"),i),end=end) 17 | 18 | def net_requests(options,method="GET",data=None,headers=default_headers,stream=False,retry_num=5): 19 | try: 20 | # se = requests.session() 21 | response = requests.request( 22 | method, 23 | options["url"], 24 | data = data, 25 | headers = headers, 26 | stream = stream, 27 | verify = False, 28 | timeout = 10, 29 | ) 30 | response.encoding = "utf8" 31 | return response 32 | except Exception as e: 33 | if retry_num > 0: 34 | return net_requests(options,method,data,headers,stream,retry_num-1) 35 | else: 36 | log_str("网络请求出错 url:{}".format(options["url"])) 37 | return None 38 | 39 | def stream_process(word,url,path): 40 | # 每次最大请求字节 41 | chunk_size = 1024 42 | # 网络请求 43 | response = net_requests(options={"url":url},stream=True) 44 | if response == None: 45 | return "NetWorkError" 46 | # 获得本次请求的字节 47 | content_size = int(response.headers['content-length']) 48 | data_count = 0 49 | 50 | with open(path, "wb") as file: 51 | for data in response.iter_content(chunk_size=chunk_size): 52 | file.write(data) 53 | data_count = data_count + len(data) 54 | now = (data_count / content_size) * 100 55 | print("\r%s下载进度:%d%%" % (word,now), end=" ") 56 | print("—— {}下载完成".format(word)) 57 | return response 58 | 59 | 60 | class GCA: 61 | """绅士小站""" 62 | def __init__(self): 63 | self.HOST = "https://ca.gca.tw" 64 | self.host = "https://ca.gca.tw/page/{}" 65 | self.root_path = os.getcwd() 66 | 67 | # cover封面图 article_url文章链接 title文章标题 68 | self.expression = """//div[@class='simple-grid-posts simple-grid-posts-grid']/div""" 69 | # self.cover_expression = """.//img//@src""" 70 | self.article_url_expression = """.//div//a//@href""" 71 | self.title_expression = """.//h3//a/text()""" 72 | # video封面&video链接 73 | self.video_cover_expression = """//span[@class='arve-embed']/meta[@itemprop="thumbnailUrl"]/@content""" 74 | self.video_cover_expression_second = """//figure[@class="wp-block-video"]/video/@poster""" 75 | 76 | self.video_url_expression = """//span[@class='arve-embed']/meta[@itemprop="contentURL"]/@content""" 77 | self.video_url_expression_second = """//figure[@class="wp-block-video"]/video/@src""" 78 | 79 | def download(self,path,content): 80 | with open(path,"wb") as f: 81 | f.write(content) 82 | 83 | def folder(self,title): 84 | title = re.sub(r'[\/:*?"<>|]','',title) 85 | title = title.replace(".","") 86 | target = os.path.join(self.root_path,title) 87 | isExists = os.path.exists(target) 88 | if not isExists: 89 | os.mkdir(target) 90 | return target 91 | 92 | def get_html(self,article_url,title): 93 | """获取输入密码后的html""" 94 | action_url = "https://ca.gca.tw/wp-login.php?action=postpass" 95 | headers = { 96 | "user-agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Safari/537.36", 97 | "referer":article_url, 98 | } 99 | data = { 100 | "post_password":title, # 密码 101 | } 102 | 103 | resp = net_requests(options={"url":action_url},method="POST",data=data,headers=headers) 104 | return resp 105 | 106 | def main(self): 107 | """主流程""" 108 | offset = 1 109 | all_info_list = [] 110 | 111 | while True: 112 | page_url = self.host.format(offset) 113 | obj = etree.HTML(net_requests(options={"url":page_url}).text) 114 | article_list = obj.xpath(self.expression) 115 | log_str("当前第{}页,共{}篇文章".format(offset,len(article_list))) 116 | 117 | for article in article_list: 118 | # article_url 119 | try: 120 | article_url = article.xpath(self.article_url_expression)[0] 121 | except Exception as e: 122 | article_url = "" 123 | # title 124 | try: 125 | title = article.xpath(self.title_expression)[0] 126 | except Exception as e: 127 | title = "" 128 | info = {"article_url":article_url,"title":title} 129 | all_info_list.append(info) 130 | 131 | # 最后一页小于60页,则跳出 132 | if len(article_list) < 60: 133 | log_str("全站文章获取完毕...共{}页{}个".format(offset,len(all_info_list))) 134 | break 135 | 136 | offset += 1 137 | # break # 测试 138 | time.sleep(1) 139 | 140 | if all_info_list == []: 141 | log_str("获取到的文章为空,可能是网站结构发现变化或其他原因") 142 | exit() 143 | 144 | # ============# start ============ # 145 | """ 146 | 文件夹 --> title(文件夹名称) 147 | 视频 --> article_url(文章链接),title(密码) --> 后面获取的视频链接 148 | """ 149 | for index,info in enumerate(all_info_list): 150 | log_str("({}/{}){} {} ".format(index+1,len(all_info_list),info["title"],info["article_url"])) 151 | if info["article_url"] == "" and info["title"] == "": 152 | log_str("当前文章标题及链接获取有误,将跳过") 153 | continue 154 | 155 | # 创建文章文件夹,title过滤一些字符 156 | article_path = self.folder(info["title"]) 157 | # 获取页面解密后的html,原始title用于解密 158 | resp = self.get_html(info["article_url"],info["title"]) 159 | obj = etree.HTML(resp.text) 160 | 161 | # video_cover视频封面 162 | try: 163 | video_cover = obj.xpath(self.video_cover_expression) 164 | if video_cover == []: 165 | video_cover = obj.xpath(self.video_cover_expression_second) 166 | except Exception as e: 167 | video_cover = [] 168 | # video_url 169 | try: 170 | video_url = obj.xpath(self.video_url_expression) 171 | if video_url == []: 172 | video_url = obj.xpath(self.video_url_expression_second) 173 | except Exception as e: 174 | video_url = [] 175 | 176 | if video_cover == [] and video_url == []: 177 | log_str("当前文章无视频及视频封面,将跳过") 178 | continue 179 | # log_str("video_cover: {} video_url: {}".format(video_cover,video_url)) 180 | 181 | # 下载视频封面video_cover 182 | cover_path = os.path.join(article_path,"{}.jpg".format(info["title"])) 183 | if os.path.exists(cover_path) == False or os.path.getsize(cover_path) < 1000: 184 | try: 185 | video_cover = video_cover[0] 186 | video_cover = self.HOST + video_cover 187 | response = stream_process("视频封面",video_cover,cover_path) 188 | except Exception as e: 189 | log_str("video_cover: {}".format(video_cover)) 190 | log_str(e) 191 | else: 192 | pass 193 | # log_str("视频封面已存在") 194 | 195 | # 下载视频 196 | video_path = os.path.join(article_path,"{}.mp4".format(info["title"])) 197 | if os.path.exists(video_path) == False or os.path.getsize(video_path) < 1000: 198 | try: 199 | video_url = video_url[0] 200 | response = stream_process("视频",video_url,video_path) 201 | if response == "NetWorkError": 202 | continue 203 | time.sleep(3) 204 | except Exception as e: 205 | log_str("video_url: {}".format(video_url)) 206 | log_str(e,response) 207 | else: 208 | pass 209 | # log_str("视频已存在") 210 | 211 | # break # 测试 212 | # =============# end ============= # 213 | 214 | 215 | gca = GCA() 216 | gca.main() -------------------------------------------------------------------------------- /0x05-绅士小站/img/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WriteCode-ChangeWorld/Tools/096dd8af8b9d1f501187ce62ab38e6be07f1518d/0x05-绅士小站/img/1.jpg -------------------------------------------------------------------------------- /0x05-绅士小站/img/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WriteCode-ChangeWorld/Tools/096dd8af8b9d1f501187ce62ab38e6be07f1518d/0x05-绅士小站/img/2.png -------------------------------------------------------------------------------- /0x05-绅士小站/img/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WriteCode-ChangeWorld/Tools/096dd8af8b9d1f501187ce62ab38e6be07f1518d/0x05-绅士小站/img/3.png -------------------------------------------------------------------------------- /0x05-绅士小站/requirement.txt: -------------------------------------------------------------------------------- 1 | requests 2 | lxml -------------------------------------------------------------------------------- /0x06-pixiv动图合成/Readme.md: -------------------------------------------------------------------------------- 1 | ## Pixiv动图合成脚本 v1.0.1——2021年4月20日 2 | 3 | --- 4 | 5 | Pixiv动图是所谓的多图轮播,只是每一张图的停留时间较长 6 | 7 | --- 8 | 9 | 功能: 10 | 11 | - [x] 合成Pixiv动图 12 | - [x] 自动清理临时文件 13 | - [x] 网络请求自动重试 14 | 15 | 16 | 17 | 使用方法: 18 | 19 | 1、安装依赖 20 | 21 | ``` 22 | pip install -r requirement.txt 23 | ``` 24 | 25 | 2、运行 26 | 27 | ``` 28 | python pix.py 29 | ``` 30 | 31 | 32 | 33 | 34 | 35 | 运行截图 36 | 37 | ![](./img/1.png) 38 | 39 | 40 | 41 | ![](./img/2.png) -------------------------------------------------------------------------------- /0x06-pixiv动图合成/img/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WriteCode-ChangeWorld/Tools/096dd8af8b9d1f501187ce62ab38e6be07f1518d/0x06-pixiv动图合成/img/1.png -------------------------------------------------------------------------------- /0x06-pixiv动图合成/img/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WriteCode-ChangeWorld/Tools/096dd8af8b9d1f501187ce62ab38e6be07f1518d/0x06-pixiv动图合成/img/2.png -------------------------------------------------------------------------------- /0x06-pixiv动图合成/pix.py: -------------------------------------------------------------------------------- 1 | import os 2 | import json 3 | import zipfile 4 | import imageio 5 | import requests 6 | 7 | class Pixiv_Gif: 8 | def __init__(self): 9 | self.headers = { 10 | # "cookie":"irst_visit_datetime_pc=2021-01-18+23%3A46%3A22; p_ab_id=4; p_ab_id_2=1; p_ab_d_id=1174719623; yuid_b=JVZjaQc; c_type=21; a_type=0; b_type=0; __utma=235335808.1683020896.1611026902.1611026902.1611026902.1; __utmz=235335808.1611026902.1.1.utmcsr=(direct)|utmccn=(direct)|utmcmd=(none); __utmv=235335808.|3=plan=normal=1^6=user_id=27858363=1^11=lang=zh=1; login_ever=yes; PHPSESSID=27858363_yDl8WSYNlr2COVTjHHENyDFS9vYBmf3E; privacy_policy_agreement=2; __cfduid=d788199f9cb94e1c9fe4771d24633d2f31613573687; p_b_type=1; __cf_bm=570419761d3f9b0aecc8f92429e5c40fe20c142d-1614658458-1800-AYaL7o9FG66P0Z2Zv/IpHa8yZzNWKXRxzJLrZgSwnfV5BPsgmrFF9Yod5ZkXVBsfW/j6aBM9P38rwLbLNRwqTkNalbMMBLglxJq14e9kn0iu; tag_view_ranking=0xsDLqCEW6~_EOd7bsGyl~Lt-oEicbBr~tgP8r-gOe_~KN7uxuR89w~uusOs0ipBx~jhuUT0OJva~yTFlPOVybE~eVxus64GZU~RcahSSzeRf~BSlt10mdnm~Ie2c51_4Sp~RokSaRBUGr~RTJMXD26Ak~KOnmT1ndWG~5oPIfUbtd6~ziiAzr_h04~yroC1pdUO-~O2wfZxfonb~faHcYIP1U0~7Y-OaPrqAv~-TeGk6mN86~jk9IzfjZ6n~PHQDP-ccQD~zZZn32I7eS~aPdvNeJ_XM~txZ9z5ByU7~dK-VDbfFxD~zIv0cf5VVk~zyKU3Q5L4C~r01unnQL0a~qNQ253s6b0~Bd2L9ZBE8q~HY55MqmzzQ~AI_aJCDFn0~WVrsHleeCL~m3EJRa33xU~y8GNntYHsi~52-15K-YXl~WcTW9TCOx9~UX647z2Emo~skx_-I2o4Y~2EpPrOnc5S~M_kcfifITK~QaiOjmwQnI~CrFcrMFJzz~_vCZ2RLsY2~ouiK2OKQ-A~tL0dW9hy1Z~v3nOtgG77A~3cT9FM3R6t~q303ip6Ui5~m47KFuLUuf~sOBG5_rfE2~BU9SQkS-zU~FqVQndhufZ~3gc3uGrU1V~cofmB2mV_d~AYsIPsa0jE~PvCsalAgmW~rNs-bh_gk3~MM6RXH_rlN~4QveACRzn3~ePN3h1AXKX~0NVHtCi70U~leIwAgTj8E~TqiZfKmSCg~lz1iXPdNUF~F-9IXA9R-9~Qg95CBAuxh~ZQorENB8GQ~8EI9ovnGxw~-0JKThjl-M~tTvZK72fmv~D0nMcn6oGk~EZQqoW9r8g~QniSV1XRdS~5BhtaeNUYC~_3oeEue7S7~vzTU7cI86f~KhVXu5CuKx~rOnsP2Q5UN~-StjcwdYwv~cbmDKjZf9z~azESOjmQSV~GFExx0uFgX~u8McsBs7WV~04qbH3MKfd~d2ro84lQRz~96TwI9y7eq~6qxeznuWaX~L7-FiupSjg~y3NlVImyly~FPCeANM2Bm~X_1kwTzaXt~-MuiEJf_Sr~0F4QqxTayD~mzJgaDwBF5~YHRjLHL-7q~g5izzko3j2", 11 | "user-agent":"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.25 Safari/537.36 Core/1.70.3741.400 QQBrowser/10.5.3863.400", 12 | "referer":"https://www.pixiv.net" 13 | } 14 | self.zip_url = "https://www.pixiv.net/ajax/illust/{}/ugoira_meta" 15 | 16 | for _ in ["gif","zip"]: 17 | _path = os.path.join(os.getcwd(),_) 18 | if not os.path.exists(_path): 19 | os.mkdir(_path) 20 | 21 | self.gif_path = os.path.join(os.getcwd(),"GIF") 22 | self.zip_path = os.path.join(os.getcwd(),"ZIP") 23 | 24 | def get_url_data(self,url,retry_num=3): 25 | try: 26 | resp = requests.get(url,headers=self.headers,timeout=15) 27 | except Exception as e: 28 | if retry_num > 0: 29 | return self.get_url_data(url,headers,retry_num=retry_num-1) 30 | else: 31 | print("网络出错,超过重试次数") 32 | exit() 33 | else: 34 | return resp 35 | 36 | def main(self,pid): 37 | resp = self.get_url_data(self.zip_url.format(pid)) 38 | zip_name = "{}.zip".format(pid) 39 | zipfile_path = os.path.join(self.zip_path,zip_name) 40 | gif_name = '{}.gif'.format(pid) 41 | giffile_path = os.path.join(self.gif_path,gif_name) 42 | 43 | # delay 44 | gif_info = json.loads(resp.text) 45 | delay = [item["delay"]/1000 for item in gif_info["body"]["frames"]] 46 | print("delay list:\n",delay) 47 | 48 | # download zip 49 | zip_originalSrc = gif_info["body"]["src"] 50 | zip_resp = self.get_url_data(zip_originalSrc) 51 | with open(zipfile_path,'ab') as f: 52 | f.write(zip_resp.content) 53 | 54 | # extract zip & delete zip 55 | with zipfile.ZipFile(zipfile_path,'r') as f: 56 | for file in f.namelist(): 57 | f.extract(file,self.zip_path) 58 | os.remove(zipfile_path) 59 | 60 | # composite gif 61 | images = [] 62 | files = os.listdir(self.zip_path) 63 | print("extract list:\n",files) 64 | for file in files: 65 | file_path = os.path.join(self.zip_path,file) 66 | images.append(imageio.imread(file_path)) 67 | imageio.mimsave(giffile_path,images,duration=delay) 68 | 69 | # delete extract file 70 | for file in files: 71 | os.remove(os.path.join(self.zip_path,file)) 72 | 73 | # success 74 | print("Download Success\n{}".format(giffile_path)) 75 | 76 | PG = Pixiv_Gif() 77 | 78 | if __name__ == '__main__': 79 | print("输入q以退出") 80 | print("接下来粘贴你要下载的动图pid吧!") 81 | while True: 82 | pid = input("\n现在输入:") 83 | if pid == "": 84 | print("pid not null") 85 | exit() 86 | elif pid == "q": 87 | print("退出成功") 88 | exit() 89 | else: 90 | PG.main(pid=pid) -------------------------------------------------------------------------------- /0x06-pixiv动图合成/requirement.txt: -------------------------------------------------------------------------------- 1 | requests 2 | imageio -------------------------------------------------------------------------------- /0x07-绅士仓库/.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | cache 3 | __pycache__ 4 | Config/*.ini 5 | !example.ini 6 | -------------------------------------------------------------------------------- /0x07-绅士仓库/Config/__init__.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | import os 3 | import logging 4 | import configparser 5 | 6 | 7 | def get_config(): 8 | # 读取配置文件 9 | run_env = 'production' 10 | if 'SERVICE_ENV' in os.environ: 11 | run_env = os.environ['SERVICE_ENV'] 12 | print("Load config [%s]" % run_env) 13 | config_path = '{}/{}.ini'.format(os.path.split(os.path.abspath(__file__))[0], run_env) 14 | if os.path.isfile(config_path): 15 | config = configparser.ConfigParser() 16 | config.read(config_path, encoding='utf-8') 17 | 18 | app_config = dict() 19 | for section in config.sections(): 20 | app_config[section] = dict(config.items(section)) 21 | 22 | app_config["RUN_ENV"] = run_env 23 | return app_config 24 | else: 25 | logging.error("Config not exist") 26 | exit() 27 | -------------------------------------------------------------------------------- /0x07-绅士仓库/Config/cangku.sql: -------------------------------------------------------------------------------- 1 | SET NAMES utf8mb4; 2 | SET FOREIGN_KEY_CHECKS = 0; 3 | 4 | -- ---------------------------- 5 | -- Table structure for cangku 6 | -- ---------------------------- 7 | DROP TABLE IF EXISTS `cangku`; 8 | CREATE TABLE `cangku` ( 9 | `id` int(11) NOT NULL, 10 | `url` varchar(2048) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, 11 | `tag` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, 12 | `user` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, 13 | `title` varchar(2048) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, 14 | `cover` varchar(2048) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, 15 | `content` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, 16 | `raw_data` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, 17 | PRIMARY KEY (`id`) USING BTREE, 18 | UNIQUE INDEX `index`(`id`) USING BTREE, 19 | INDEX `tag_index`(`tag`) USING BTREE 20 | ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci ROW_FORMAT = Dynamic; 21 | 22 | SET FOREIGN_KEY_CHECKS = 1; 23 | -------------------------------------------------------------------------------- /0x07-绅士仓库/Config/gca_tw.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Navicat Premium Data Transfer 3 | 4 | Source Server : ASUS MySQL 5 | Source Server Type : MySQL 6 | Source Server Version : 50734 7 | Source Host : asus.host.wiolfi.cn:12806 8 | Source Schema : videwolf 9 | 10 | Target Server Type : MySQL 11 | Target Server Version : 50734 12 | File Encoding : 65001 13 | 14 | Date: 02/07/2021 00:11:31 15 | */ 16 | 17 | SET NAMES utf8mb4; 18 | SET FOREIGN_KEY_CHECKS = 0; 19 | 20 | -- ---------------------------- 21 | -- Table structure for gca_tw 22 | -- ---------------------------- 23 | DROP TABLE IF EXISTS `gca_tw`; 24 | CREATE TABLE `gca_tw` ( 25 | `id` int(11) NOT NULL, 26 | `url` varchar(2048) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, 27 | `tag` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, 28 | `title` varchar(2048) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, 29 | `cover` varchar(2048) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, 30 | `video` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, 31 | `download` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, 32 | `raw_data` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, 33 | PRIMARY KEY (`id`) USING BTREE, 34 | UNIQUE INDEX `index`(`id`) USING BTREE, 35 | INDEX `tag_index`(`tag`) USING BTREE 36 | ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci ROW_FORMAT = Dynamic; 37 | 38 | SET FOREIGN_KEY_CHECKS = 1; 39 | -------------------------------------------------------------------------------- /0x07-绅士仓库/Config/openssl.cnf: -------------------------------------------------------------------------------- 1 | # 2 | # OpenSSL example configuration file. 3 | # This is mostly being used for generation of certificate requests. 4 | # 5 | 6 | # Note that you can include other files from the main configuration 7 | # file using the .include directive. 8 | #.include filename 9 | 10 | # This definition stops the following lines choking if HOME isn't 11 | # defined. 12 | HOME = . 13 | 14 | # Extra OBJECT IDENTIFIER info: 15 | #oid_file = $ENV::HOME/.oid 16 | oid_section = new_oids 17 | 18 | # System default 19 | openssl_conf = default_conf 20 | 21 | # To use this configuration file with the "-extfile" option of the 22 | # "openssl x509" utility, name here the section containing the 23 | # X.509v3 extensions to use: 24 | # extensions = 25 | # (Alternatively, use a configuration file that has only 26 | # X.509v3 extensions in its main [= default] section.) 27 | 28 | [ new_oids ] 29 | 30 | # We can add new OIDs in here for use by 'ca', 'req' and 'ts'. 31 | # Add a simple OID like this: 32 | # testoid1=1.2.3.4 33 | # Or use config file substitution like this: 34 | # testoid2=${testoid1}.5.6 35 | 36 | # Policies used by the TSA examples. 37 | tsa_policy1 = 1.2.3.4.1 38 | tsa_policy2 = 1.2.3.4.5.6 39 | tsa_policy3 = 1.2.3.4.5.7 40 | 41 | #################################################################### 42 | [ ca ] 43 | default_ca = CA_default # The default ca section 44 | 45 | #################################################################### 46 | [ CA_default ] 47 | 48 | dir = ./demoCA # Where everything is kept 49 | certs = $dir/certs # Where the issued certs are kept 50 | crl_dir = $dir/crl # Where the issued crl are kept 51 | database = $dir/index.txt # database index file. 52 | #unique_subject = no # Set to 'no' to allow creation of 53 | # several certs with same subject. 54 | new_certs_dir = $dir/newcerts # default place for new certs. 55 | 56 | certificate = $dir/cacert.pem # The CA certificate 57 | serial = $dir/serial # The current serial number 58 | crlnumber = $dir/crlnumber # the current crl number 59 | # must be commented out to leave a V1 CRL 60 | crl = $dir/crl.pem # The current CRL 61 | private_key = $dir/private/cakey.pem# The private key 62 | 63 | x509_extensions = usr_cert # The extensions to add to the cert 64 | 65 | # Comment out the following two lines for the "traditional" 66 | # (and highly broken) format. 67 | name_opt = ca_default # Subject Name options 68 | cert_opt = ca_default # Certificate field options 69 | 70 | # Extension copying option: use with caution. 71 | # copy_extensions = copy 72 | 73 | # Extensions to add to a CRL. Note: Netscape communicator chokes on V2 CRLs 74 | # so this is commented out by default to leave a V1 CRL. 75 | # crlnumber must also be commented out to leave a V1 CRL. 76 | # crl_extensions = crl_ext 77 | 78 | default_days = 365 # how long to certify for 79 | default_crl_days= 30 # how long before next CRL 80 | default_md = default # use public key default MD 81 | preserve = no # keep passed DN ordering 82 | 83 | # A few difference way of specifying how similar the request should look 84 | # For type CA, the listed attributes must be the same, and the optional 85 | # and supplied fields are just that :-) 86 | policy = policy_match 87 | 88 | # For the CA policy 89 | [ policy_match ] 90 | countryName = match 91 | stateOrProvinceName = match 92 | organizationName = match 93 | organizationalUnitName = optional 94 | commonName = supplied 95 | emailAddress = optional 96 | 97 | # For the 'anything' policy 98 | # At this point in time, you must list all acceptable 'object' 99 | # types. 100 | [ policy_anything ] 101 | countryName = optional 102 | stateOrProvinceName = optional 103 | localityName = optional 104 | organizationName = optional 105 | organizationalUnitName = optional 106 | commonName = supplied 107 | emailAddress = optional 108 | 109 | #################################################################### 110 | [ req ] 111 | default_bits = 2048 112 | default_keyfile = privkey.pem 113 | distinguished_name = req_distinguished_name 114 | attributes = req_attributes 115 | x509_extensions = v3_ca # The extensions to add to the self signed cert 116 | 117 | # Passwords for private keys if not present they will be prompted for 118 | # input_password = secret 119 | # output_password = secret 120 | 121 | # This sets a mask for permitted string types. There are several options. 122 | # default: PrintableString, T61String, BMPString. 123 | # pkix : PrintableString, BMPString (PKIX recommendation before 2004) 124 | # utf8only: only UTF8Strings (PKIX recommendation after 2004). 125 | # nombstr : PrintableString, T61String (no BMPStrings or UTF8Strings). 126 | # MASK:XXXX a literal mask value. 127 | # WARNING: ancient versions of Netscape crash on BMPStrings or UTF8Strings. 128 | string_mask = utf8only 129 | 130 | # req_extensions = v3_req # The extensions to add to a certificate request 131 | 132 | [ req_distinguished_name ] 133 | countryName = Country Name (2 letter code) 134 | countryName_default = AU 135 | countryName_min = 2 136 | countryName_max = 2 137 | 138 | stateOrProvinceName = State or Province Name (full name) 139 | stateOrProvinceName_default = Some-State 140 | 141 | localityName = Locality Name (eg, city) 142 | 143 | 0.organizationName = Organization Name (eg, company) 144 | 0.organizationName_default = Internet Widgits Pty Ltd 145 | 146 | # we can do this but it is not needed normally :-) 147 | #1.organizationName = Second Organization Name (eg, company) 148 | #1.organizationName_default = World Wide Web Pty Ltd 149 | 150 | organizationalUnitName = Organizational Unit Name (eg, section) 151 | #organizationalUnitName_default = 152 | 153 | commonName = Common Name (e.g. server FQDN or YOUR name) 154 | commonName_max = 64 155 | 156 | emailAddress = Email Address 157 | emailAddress_max = 64 158 | 159 | # SET-ex3 = SET extension number 3 160 | 161 | [ req_attributes ] 162 | challengePassword = A challenge password 163 | challengePassword_min = 4 164 | challengePassword_max = 20 165 | 166 | unstructuredName = An optional company name 167 | 168 | [ usr_cert ] 169 | 170 | # These extensions are added when 'ca' signs a request. 171 | 172 | # This goes against PKIX guidelines but some CAs do it and some software 173 | # requires this to avoid interpreting an end user certificate as a CA. 174 | 175 | basicConstraints=CA:FALSE 176 | 177 | # Here are some examples of the usage of nsCertType. If it is omitted 178 | # the certificate can be used for anything *except* object signing. 179 | 180 | # This is OK for an SSL server. 181 | # nsCertType = server 182 | 183 | # For an object signing certificate this would be used. 184 | # nsCertType = objsign 185 | 186 | # For normal client use this is typical 187 | # nsCertType = client, email 188 | 189 | # and for everything including object signing: 190 | # nsCertType = client, email, objsign 191 | 192 | # This is typical in keyUsage for a client certificate. 193 | # keyUsage = nonRepudiation, digitalSignature, keyEncipherment 194 | 195 | # This will be displayed in Netscape's comment listbox. 196 | nsComment = "OpenSSL Generated Certificate" 197 | 198 | # PKIX recommendations harmless if included in all certificates. 199 | subjectKeyIdentifier=hash 200 | authorityKeyIdentifier=keyid,issuer 201 | 202 | # This stuff is for subjectAltName and issuerAltname. 203 | # Import the email address. 204 | # subjectAltName=email:copy 205 | # An alternative to produce certificates that aren't 206 | # deprecated according to PKIX. 207 | # subjectAltName=email:move 208 | 209 | # Copy subject details 210 | # issuerAltName=issuer:copy 211 | 212 | #nsCaRevocationUrl = http://www.domain.dom/ca-crl.pem 213 | #nsBaseUrl 214 | #nsRevocationUrl 215 | #nsRenewalUrl 216 | #nsCaPolicyUrl 217 | #nsSslServerName 218 | 219 | # This is required for TSA certificates. 220 | # extendedKeyUsage = critical,timeStamping 221 | 222 | [ v3_req ] 223 | 224 | # Extensions to add to a certificate request 225 | 226 | basicConstraints = CA:FALSE 227 | keyUsage = nonRepudiation, digitalSignature, keyEncipherment 228 | 229 | [ v3_ca ] 230 | 231 | 232 | # Extensions for a typical CA 233 | 234 | 235 | # PKIX recommendation. 236 | 237 | subjectKeyIdentifier=hash 238 | 239 | authorityKeyIdentifier=keyid:always,issuer 240 | 241 | basicConstraints = critical,CA:true 242 | 243 | # Key usage: this is typical for a CA certificate. However since it will 244 | # prevent it being used as an test self-signed certificate it is best 245 | # left out by default. 246 | # keyUsage = cRLSign, keyCertSign 247 | 248 | # Some might want this also 249 | # nsCertType = sslCA, emailCA 250 | 251 | # Include email address in subject alt name: another PKIX recommendation 252 | # subjectAltName=email:copy 253 | # Copy issuer details 254 | # issuerAltName=issuer:copy 255 | 256 | # DER hex encoding of an extension: beware experts only! 257 | # obj=DER:02:03 258 | # Where 'obj' is a standard or added object 259 | # You can even override a supported extension: 260 | # basicConstraints= critical, DER:30:03:01:01:FF 261 | 262 | [ crl_ext ] 263 | 264 | # CRL extensions. 265 | # Only issuerAltName and authorityKeyIdentifier make any sense in a CRL. 266 | 267 | # issuerAltName=issuer:copy 268 | authorityKeyIdentifier=keyid:always 269 | 270 | [ proxy_cert_ext ] 271 | # These extensions should be added when creating a proxy certificate 272 | 273 | # This goes against PKIX guidelines but some CAs do it and some software 274 | # requires this to avoid interpreting an end user certificate as a CA. 275 | 276 | basicConstraints=CA:FALSE 277 | 278 | # Here are some examples of the usage of nsCertType. If it is omitted 279 | # the certificate can be used for anything *except* object signing. 280 | 281 | # This is OK for an SSL server. 282 | # nsCertType = server 283 | 284 | # For an object signing certificate this would be used. 285 | # nsCertType = objsign 286 | 287 | # For normal client use this is typical 288 | # nsCertType = client, email 289 | 290 | # and for everything including object signing: 291 | # nsCertType = client, email, objsign 292 | 293 | # This is typical in keyUsage for a client certificate. 294 | # keyUsage = nonRepudiation, digitalSignature, keyEncipherment 295 | 296 | # This will be displayed in Netscape's comment listbox. 297 | nsComment = "OpenSSL Generated Certificate" 298 | 299 | # PKIX recommendations harmless if included in all certificates. 300 | subjectKeyIdentifier=hash 301 | authorityKeyIdentifier=keyid,issuer 302 | 303 | # This stuff is for subjectAltName and issuerAltname. 304 | # Import the email address. 305 | # subjectAltName=email:copy 306 | # An alternative to produce certificates that aren't 307 | # deprecated according to PKIX. 308 | # subjectAltName=email:move 309 | 310 | # Copy subject details 311 | # issuerAltName=issuer:copy 312 | 313 | #nsCaRevocationUrl = http://www.domain.dom/ca-crl.pem 314 | #nsBaseUrl 315 | #nsRevocationUrl 316 | #nsRenewalUrl 317 | #nsCaPolicyUrl 318 | #nsSslServerName 319 | 320 | # This really needs to be in place for it to be a proxy certificate. 321 | proxyCertInfo=critical,language:id-ppl-anyLanguage,pathlen:3,policy:foo 322 | 323 | #################################################################### 324 | [ tsa ] 325 | 326 | default_tsa = tsa_config1 # the default TSA section 327 | 328 | [ tsa_config1 ] 329 | 330 | # These are used by the TSA reply generation only. 331 | dir = ./demoCA # TSA root directory 332 | serial = $dir/tsaserial # The current serial number (mandatory) 333 | crypto_device = builtin # OpenSSL engine to use for signing 334 | signer_cert = $dir/tsacert.pem # The TSA signing certificate 335 | # (optional) 336 | certs = $dir/cacert.pem # Certificate chain to include in reply 337 | # (optional) 338 | signer_key = $dir/private/tsakey.pem # The TSA private key (optional) 339 | signer_digest = sha256 # Signing digest to use. (Optional) 340 | default_policy = tsa_policy1 # Policy if request did not specify it 341 | # (optional) 342 | other_policies = tsa_policy2, tsa_policy3 # acceptable policies (optional) 343 | digests = sha1, sha256, sha384, sha512 # Acceptable message digests (mandatory) 344 | accuracy = secs:1, millisecs:500, microsecs:100 # (optional) 345 | clock_precision_digits = 0 # number of digits after dot. (optional) 346 | ordering = yes # Is ordering defined for timestamps? 347 | # (optional, default: no) 348 | tsa_name = yes # Must the TSA name be included in the reply? 349 | # (optional, default: no) 350 | ess_cert_id_chain = no # Must the ESS cert id chain be included? 351 | # (optional, default: no) 352 | ess_cert_id_alg = sha1 # algorithm to compute certificate 353 | # identifier (optional, default: sha1) 354 | [default_conf] 355 | ssl_conf = ssl_sect 356 | 357 | [ssl_sect] 358 | system_default = system_default_sect 359 | 360 | [system_default_sect] 361 | MinProtocol = TLSv1.0 362 | CipherString = DEFAULT@SECLEVEL=1 363 | -------------------------------------------------------------------------------- /0x07-绅士仓库/Kit/__init__.py: -------------------------------------------------------------------------------- 1 | from .kit import * 2 | from .chrome import env_check 3 | from .chrome import run_browser 4 | -------------------------------------------------------------------------------- /0x07-绅士仓库/Kit/chrome.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | import os 3 | import re 4 | import Kit 5 | import stat 6 | import zipfile 7 | import requests 8 | import subprocess 9 | from selenium import webdriver 10 | 11 | # 引入Windows特定库 12 | if Kit.run_platform() == "windows": 13 | import winreg 14 | 15 | # 全局变量 16 | version_re = re.compile(r'^[1-9]\d*\.\d*.\d*') 17 | 18 | 19 | def run_browser(driver_path, headless="Yes"): 20 | driver_path = os.path.abspath(driver_path) + "/chromedriver" 21 | option = webdriver.ChromeOptions() 22 | if headless == "Yes": 23 | option.add_argument('--headless') 24 | option.add_argument('--no-sandbox') 25 | option.add_argument('--disable-gpu') 26 | browser = webdriver.Chrome(driver_path, options=option) 27 | return browser 28 | 29 | 30 | def env_check(cache_path): 31 | print("[INFO]", "Check chrome driver env...") 32 | cache_path = os.path.abspath(cache_path) 33 | 34 | chrome_version = get_chrome_version() 35 | driver_version = get_driver_version(cache_path) 36 | 37 | if chrome_version == "0": 38 | print("[INFO]", "Please install chrome browser") 39 | return False 40 | 41 | if driver_version == "0": 42 | # Download driver 43 | print("[INFO]", "Download chrome driver") 44 | return download_driver(chrome_version, cache_path) 45 | 46 | if version_re.findall(chrome_version)[0] != version_re.findall(driver_version)[0]: 47 | # Update driver 48 | print("[INFO]", "Update chrome driver") 49 | return download_driver(chrome_version, cache_path) 50 | 51 | print("[INFO]", "Chrome driver check pass") 52 | return True # Check success 53 | 54 | 55 | def get_chrome_version(): 56 | print("[INFO]", "Check chrome version...") 57 | if Kit.run_platform() == "windows": 58 | try: 59 | # 从注册表中获得版本号 60 | key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, r"Software\Google\Chrome\BLBeacon") 61 | version, _ = winreg.QueryValueEx(key, "version") 62 | 63 | print("[INFO]", "Current chrome Version: {}".format(version)) # 这步打印会在命令行窗口显示 64 | return version 65 | except WindowsError as e: 66 | print("[INFO]", "Check chrome failed:{}".format(e)) 67 | return "0" 68 | else: 69 | try: 70 | cmd = r"google-chrome-stable --version" 71 | out, err = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate() 72 | out = out.decode("utf-8") 73 | version = out.split(" ")[2] # 拆分回显字符串,获取版本号 74 | print("[INFO]", "Current chrome Version:{}".format(version)) 75 | return version 76 | except WindowsError as e: 77 | print("[INFO]", "Check chrome failed:{}".format(e)) 78 | return "0" 79 | 80 | 81 | def get_driver_version(driver_path): 82 | print("[INFO]", "Check driver version...") 83 | if Kit.run_platform() == "windows": 84 | try: 85 | # 执行cmd命令并接收命令回显 86 | cmd = r'"{}/chromedriver" --version'.format(driver_path) 87 | out, err = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate() 88 | out = out.decode("utf-8") 89 | version = out.split(" ")[1] # 拆分回显字符串,获取版本号 90 | print("[INFO]", "Current driver Version:{}".format(version)) 91 | return version 92 | except IndexError as e: 93 | print("[INFO]", "Check driver failed:{}".format(e)) 94 | return "0" 95 | else: 96 | try: 97 | # 执行cmd命令并接收命令回显 98 | cmd = r"{}/chromedriver --version".format(driver_path) 99 | out, err = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate() 100 | out = out.decode("utf-8") 101 | version = out.split(" ")[1] # 拆分回显字符串,获取版本号 102 | print("[INFO]", "Current driver Version:{}".format(version)) 103 | return version 104 | except IndexError as e: 105 | print("[INFO]", "Check driver failed:{}".format(e)) 106 | return "0" 107 | 108 | 109 | def download_driver(version, driver_path): 110 | print("[INFO]", "Download driver on", Kit.run_platform()) 111 | driver_path = os.path.abspath(driver_path) 112 | 113 | # 获取淘宝镜像列表 114 | http_res = requests.get("http://npm.taobao.org/mirrors/chromedriver") 115 | mirror_list = re.findall(r'(.*)', http_res.text) 116 | 117 | # 检索驱动版本列表 118 | url_path = "" 119 | version_prefix = version_re.findall(version)[0] 120 | for mirrors in mirror_list: 121 | if mirrors[1].startswith(version_prefix): 122 | url_path = mirrors[0] 123 | 124 | print("[INFO]", "Download driver version:", url_path.split("/")[-1]) 125 | if Kit.run_platform() == "windows": 126 | file_url = "http://npm.taobao.org/{}/chromedriver_win32.zip".format(url_path) 127 | elif Kit.run_platform() == "linux": 128 | file_url = "http://npm.taobao.org/{}/chromedriver_linux64.zip".format(url_path) 129 | elif Kit.run_platform() == "darwin": 130 | file_url = "http://npm.taobao.org/{}/chromedriver_mac64.zip".format(url_path) 131 | else: 132 | print("[ERR]", "Not found chrome driver") 133 | return False 134 | 135 | print("[INFO]", "Download driver url:", file_url) 136 | driver_res = requests.get(file_url) 137 | 138 | # 写入压缩文件 139 | zip_path = driver_path + "/chromedriver.zip" 140 | zip_file = open(zip_path, "wb") 141 | zip_file.write(driver_res.content) 142 | zip_file.close() 143 | 144 | # 下载完成后解压 145 | zip_file = zipfile.ZipFile(zip_path, "r") 146 | for fileM in zip_file.namelist(): 147 | zip_file.extract(fileM, os.path.dirname(zip_path)) 148 | zip_file.close() 149 | 150 | # 删除残留文件 151 | os.remove(zip_path) 152 | 153 | # 授予执行权限 154 | if Kit.run_platform() == "linux": 155 | os.chmod(driver_path + "/chromedriver", stat.S_IXGRP) 156 | 157 | print("[INFO]", "Download chrome driver finish") 158 | return True 159 | -------------------------------------------------------------------------------- /0x07-绅士仓库/Kit/kit.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | import os 3 | import re 4 | import sys 5 | import uuid 6 | import time 7 | import random 8 | import string 9 | import inspect 10 | import hashlib 11 | import platform 12 | import pymysql 13 | 14 | 15 | # from bs4 import BeautifulSoup 16 | 17 | # MySQL 18 | def mysql_conn(config, db_key): 19 | config[db_key]['port'] = int(config[db_key]['port']) 20 | conn = pymysql.connect(**config[db_key]) 21 | return conn 22 | 23 | 24 | # Print tools 25 | def _print(message, code=None, tag=None, end=None): 26 | if tag is None: 27 | message = '[{}] {}'.format(tag, message) 28 | if code is not None: 29 | message = '\033[0;{}m{}\033[0m'.format(code, message) 30 | print(message, end=end) 31 | 32 | 33 | def print_red(message, tag="ERROR", end=None): 34 | _print(message, 31, tag, end) # 红色 35 | 36 | 37 | def print_green(message, tag="DONE", end='\n'): 38 | _print(message, 32, tag, end) # 绿色 39 | 40 | 41 | def print_yellow(message, tag="WARNING", end='\n'): 42 | _print(message, 33, tag, end) # 黄色 43 | 44 | 45 | def print_blue(message, tag="BEG", end='\n'): 46 | _print(message, 34, tag, end) # 深蓝色 47 | 48 | 49 | def print_purple(message, tag="MID", end='\n'): 50 | _print(message, 35, tag, end) # 紫色 51 | 52 | 53 | def print_azure(message, tag="END", end='\n'): 54 | _print(message, 36, tag, end) # 浅蓝色 55 | 56 | 57 | def print_white(message, tag="INFO", end='\n'): 58 | _print(message, 37, tag, end) # 白色 59 | 60 | 61 | def print_none(message, tag="DEBUG", end='\n'): 62 | _print(message, None, tag, end) # 默认 63 | 64 | 65 | def process_bar(now, total, attach=''): 66 | # 在窗口底部动态显示进度条 67 | rate = now / total 68 | rate_num = int(rate * 100) 69 | bar_length = int(rate_num / 2) 70 | if rate_num == 100: 71 | bar = 'Pid:[%5d]: %s' % (os.getpid(), attach.center(10, " ")) 72 | bar = '\r' + bar[0:40] 73 | bar += '%s>%d%%\n' % ('=' * bar_length, rate_num) 74 | else: 75 | bar = 'Pid:[%5d]: %s' % (os.getpid(), attach.center(10, " ")) 76 | bar = '\r' + bar[0:40] 77 | bar += '%s>%d%%' % ('=' * bar_length, rate_num) 78 | sys.stdout.write(bar) 79 | sys.stdout.flush() 80 | 81 | 82 | # Time tools 83 | 84 | def unix_time(unit=1): 85 | return int(time.time() * unit) 86 | 87 | 88 | def str_time(pattern='%Y-%m-%d %H:%M:%S', timing=None): 89 | if timing is None: 90 | timing = time.time() 91 | return time.strftime(pattern, time.localtime(timing)) 92 | 93 | 94 | def format_time(time_obj, pattern='%Y-%m-%d %H:%M:%S'): 95 | return time.strftime(pattern, time_obj) 96 | 97 | 98 | def datetime2unix(timing): 99 | return int(time.mktime(timing.timetuple())) 100 | 101 | 102 | def timestamp2unix(time_string, pattern='%Y-%m-%d %H:%M:%S'): 103 | time_array = time.strptime(time_string, pattern) 104 | return int(time.mktime(time_array)) 105 | 106 | 107 | def unix2timestamp(u_time, pattern='%Y-%m-%d %H:%M:%S'): 108 | return time.strftime(pattern, time.localtime(u_time)) 109 | 110 | 111 | # Calc tools 112 | 113 | def func_name(fa=1): 114 | return inspect.stack()[fa][3] 115 | 116 | 117 | def random_code(): 118 | return str(uuid.uuid1()).split('-')[0] 119 | 120 | 121 | def random_string(length, chars=string.digits + string.ascii_letters): 122 | return ''.join(random.choice(chars) for _ in range(length)) 123 | 124 | 125 | def calc_sha1(seed): 126 | seed = str(seed).encode('utf-8') 127 | sha1 = hashlib.sha1() 128 | sha1.update(seed) 129 | return sha1.hexdigest() 130 | 131 | 132 | def calc_md5(seed): 133 | seed = str(seed).encode('utf-8') 134 | md5 = hashlib.md5() 135 | md5.update(seed) 136 | return md5.hexdigest() 137 | 138 | 139 | # def parse_xml(data): 140 | # xml = re.sub(r'', lambda m: m.group(1), data) 141 | # xml = BeautifulSoup(xml, 'lxml') 142 | # xml = xml.html.body.xml 143 | # return xml 144 | 145 | 146 | def cpu_core(): 147 | if run_platform() == "windows": 148 | return int(os.popen("echo %NUMBER_OF_PROCESSORS%").read()) 149 | elif run_platform() == "linux": 150 | return int(os.popen(r"cat /proc/cpuinfo | grep 'cpu cores' | uniq | awk '{print $4}'").read()) 151 | else: 152 | return 0 153 | 154 | 155 | def run_platform(): 156 | # windows / linux / darwin 157 | return platform.system().lower() 158 | 159 | 160 | # File tools 161 | 162 | 163 | def code_dir(): 164 | file = os.path.abspath(__file__) 165 | return os.path.dirname(file) 166 | 167 | 168 | def code_path(): 169 | return os.path.abspath(__file__) 170 | 171 | 172 | def legalize_name(name): 173 | legal_name = re.sub(r"[\/\\\:\*\?\"\<\>\|\s']", '_', name) 174 | legal_name = re.sub(r'[‘’]', '_', legal_name) 175 | if len(legal_name) == 0: 176 | return 'null' 177 | return legal_name 178 | 179 | 180 | def delete_old_file(dir_path, expire_time): 181 | time_now = unix_time() 182 | dir_path = os.path.abspath(dir_path) 183 | for file in os.listdir(dir_path): 184 | file_path = '{}/{}'.format(dir_path, file) 185 | creat_time = os.path.getctime(file_path) 186 | if time_now > creat_time + expire_time: 187 | os.remove(file_path) 188 | 189 | 190 | # Network tools 191 | 192 | def parse_cookie(cookies): 193 | if cookies == "": 194 | return {} 195 | cookie_dict = {} 196 | for line in cookies.split(';'): 197 | name, value = line.strip().split('=', 1) 198 | cookie_dict[name] = value 199 | return cookie_dict 200 | 201 | 202 | def random_ip(model="all"): 203 | if model == "A": 204 | return "%d.%d.%d.%d" % (random.randint(1, 126), random.randint(1, 254), 205 | random.randint(1, 254), random.randint(1, 254)) 206 | elif model == "B": 207 | return "%d.%d.%d.%d" % (random.randint(128, 191), random.randint(1, 254), 208 | random.randint(1, 254), random.randint(1, 254)) 209 | elif model == "C": 210 | return "%d.%d.%d.%d" % (random.randint(192, 223), random.randint(1, 254), 211 | random.randint(1, 254), random.randint(1, 254)) 212 | else: 213 | return "%d.%d.%d.%d" % (random.randint(1, 254), random.randint(1, 254), 214 | random.randint(1, 254), random.randint(1, 254)) 215 | -------------------------------------------------------------------------------- /0x07-绅士仓库/Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | url = "https://pypi.org/simple" 3 | verify_ssl = true 4 | name = "pypi" 5 | 6 | [packages] 7 | selenium = "*" 8 | requests = "*" 9 | pymysql = "*" 10 | 11 | [dev-packages] 12 | 13 | [requires] 14 | python_version = "3.8" 15 | -------------------------------------------------------------------------------- /0x07-绅士仓库/Pipfile.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_meta": { 3 | "hash": { 4 | "sha256": "1a52d96a1ef1b2de27506c7953d1206472f1a90be26e37fcc2bc3acf0adbe8f4" 5 | }, 6 | "pipfile-spec": 6, 7 | "requires": { 8 | "python_version": "3.8" 9 | }, 10 | "sources": [ 11 | { 12 | "name": "pypi", 13 | "url": "https://pypi.org/simple", 14 | "verify_ssl": true 15 | } 16 | ] 17 | }, 18 | "default": { 19 | "certifi": { 20 | "hashes": [ 21 | "sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3", 22 | "sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18" 23 | ], 24 | "index": "pypi", 25 | "version": "==2022.12.7" 26 | }, 27 | "chardet": { 28 | "hashes": [ 29 | "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa", 30 | "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5" 31 | ], 32 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", 33 | "version": "==4.0.0" 34 | }, 35 | "idna": { 36 | "hashes": [ 37 | "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6", 38 | "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0" 39 | ], 40 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 41 | "version": "==2.10" 42 | }, 43 | "pymysql": { 44 | "hashes": [ 45 | "sha256:41fc3a0c5013d5f039639442321185532e3e2c8924687abe6537de157d403641", 46 | "sha256:816927a350f38d56072aeca5dfb10221fe1dc653745853d30a216637f5d7ad36" 47 | ], 48 | "index": "pypi", 49 | "version": "==1.0.2" 50 | }, 51 | "requests": { 52 | "hashes": [ 53 | "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804", 54 | "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e" 55 | ], 56 | "index": "pypi", 57 | "version": "==2.25.1" 58 | }, 59 | "selenium": { 60 | "hashes": [ 61 | "sha256:2d7131d7bc5a5b99a2d9b04aaf2612c411b03b8ca1b1ee8d3de5845a9be2cb3c", 62 | "sha256:deaf32b60ad91a4611b98d8002757f29e6f2c2d5fcaf202e1c9ad06d6772300d" 63 | ], 64 | "index": "pypi", 65 | "version": "==3.141.0" 66 | }, 67 | "urllib3": { 68 | "hashes": [ 69 | "sha256:47cc05d99aaa09c9e72ed5809b60e7ba354e64b59c9c173ac3018642d8bb41fc", 70 | "sha256:c083dd0dce68dbfbe1129d5271cb90f9447dea7d52097c6e0126120c521ddea8" 71 | ], 72 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", 73 | "version": "==1.26.13" 74 | } 75 | }, 76 | "develop": {} 77 | } 78 | -------------------------------------------------------------------------------- /0x07-绅士仓库/login.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | 4 | -------------------------------------------------------------------------------- /0x07-绅士仓库/main.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | import os 3 | import re 4 | import Kit 5 | import json 6 | import time 7 | import Config 8 | import urllib3 9 | import pymysql 10 | import requests 11 | from requests import utils 12 | from selenium.webdriver.common.by import By 13 | from selenium.webdriver.support.ui import WebDriverWait 14 | from selenium.webdriver.support import expected_conditions as EC 15 | 16 | urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) 17 | 18 | 19 | def main(): 20 | config = Config.get_config() 21 | print("[INFO]", "Start at", Kit.str_time()) 22 | 23 | # Check environment 24 | cache_path = config['BASE']['cache_path'] 25 | os.makedirs(cache_path, exist_ok=True) 26 | if Kit.env_check(cache_path) is False: 27 | print("[ERR]", "Chrome driver run environment not found") 28 | return exit(1) 29 | 30 | # User login 31 | cookies = login(config) 32 | 33 | # Fetch archives 34 | session = requests.Session() 35 | cookies_jar = requests.utils.cookiejar_from_dict(cookies) 36 | session.cookies = cookies_jar 37 | session.headers = {"User-Agent": config['BASE']['user_agent']} 38 | 39 | conn = Kit.mysql_conn(config, "MYSQL") 40 | cursor = conn.cursor() 41 | 42 | for pid in range(189900, 1, -1): 43 | sql = "SELECT COUNT(*) FROM `cangku` WHERE `id`=%s" 44 | cursor.execute(sql, pid) 45 | count = int(cursor.fetchone()[0]) 46 | if count == 1: 47 | continue 48 | 49 | for i in range(3): 50 | try: 51 | fetch_info(config, conn, session, pid) 52 | break 53 | except Exception as e: 54 | error_record(conn, pid, 000) 55 | Kit.print_red("RE{}".format(i)) 56 | print(e) 57 | 58 | 59 | def login(config): 60 | print("[INFO]", "Opening website") 61 | cache_path = config['BASE']['cache_path'] 62 | browser = Kit.run_browser(cache_path, config['BASE']['headless']) 63 | browser.implicitly_wait(10) 64 | browser.get("https://cangku.io/login") 65 | form_group = browser.find_elements_by_class_name("form-group") 66 | form_group[0].find_element_by_tag_name("input").send_keys(config['USER']['username']) 67 | form_group[1].find_element_by_tag_name("input").send_keys(config['USER']['password']) 68 | browser.find_element_by_class_name("btn-danger").click() 69 | WebDriverWait(browser, 10).until(EC.visibility_of_element_located((By.CLASS_NAME, "global-announce"))) 70 | cookies = {} 71 | for item in browser.get_cookies(): 72 | cookies[item["name"]] = item["value"] 73 | 74 | browser.quit() 75 | return cookies 76 | 77 | 78 | def fetch_info(config, conn, session, pid): 79 | # Fetch info data 80 | print("[INFO]", "Fetch page {}...".format(pid), end="") 81 | params = {"include": "user,tags,categories,favorited,upvoted"} 82 | res = session.get("https://cangku.io/api/v1/post/info?id={}".format(pid), params=params, 83 | proxies=config["PROXY"], verify=False) 84 | if res.status_code == 403: 85 | error_record(conn, pid, 403) 86 | Kit.print_red("BAN 403") 87 | return False 88 | 89 | # Parser json 90 | page_data = json.loads(res.text) 91 | if page_data["status_code"] == 404: 92 | error_record(conn, pid, 404) 93 | Kit.print_red("ERR 404") 94 | return False 95 | elif page_data["status_code"] == 200: 96 | tag_name = page_data["data"]["title"].split("]")[0].split("[")[-1] 97 | if tag_name == page_data["data"]["title"]: 98 | tag_name = page_data["data"]["categories"][0]["name"] 99 | sql_data = { 100 | "pid": page_data["data"]["id"], 101 | "url": "https://cangku.io/archives/{}".format(pid), 102 | "tag": tag_name, 103 | "user": page_data["data"]["user_id"], 104 | "title": page_data["data"]["title"], 105 | "cover": page_data["data"]["thumb"], 106 | "content": page_data["data"]["content"], 107 | "raw_data": res.text 108 | } 109 | sql = "REPLACE INTO `cangku`(id,url,tag,user,title,cover,content,raw_data)VALUES(%s,%s,%s,%s,%s,%s,%s,%s)" 110 | cursor = conn.cursor() 111 | cursor.execute(sql, args=list(sql_data.values())) 112 | conn.commit() 113 | 114 | Kit.print_green("OK") 115 | print("Info: {} // {} // {}".format(pid, sql_data["tag"], sql_data["title"])) 116 | return True 117 | else: 118 | error_record(conn, pid, page_data["status_code"]) 119 | Kit.print_red("ERR ???") 120 | print("Error status", pid, page_data["status_code"], page_data["message"]) 121 | return False 122 | 123 | 124 | def error_record(conn, pid, status): 125 | cursor = conn.cursor() 126 | sql = "REPLACE INTO `cangku`(id,url,tag,user,title,cover,content,raw_data)VALUES(%s,%s,%s,%s,%s,%s,%s,%s)" 127 | cursor.execute(sql, args=[pid, "https://cangku.io/archives/{}".format(pid), status, "无法访问", "", "", "", ""]) 128 | conn.commit() 129 | 130 | 131 | if __name__ == '__main__': 132 | main() 133 | -------------------------------------------------------------------------------- /0x08-绅士小站2/.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | cache 3 | __pycache__ 4 | Config/*.ini 5 | !example.ini 6 | -------------------------------------------------------------------------------- /0x08-绅士小站2/Config/__init__.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | import os 3 | import logging 4 | import configparser 5 | 6 | 7 | def get_config(): 8 | # 读取配置文件 9 | run_env = 'production' 10 | if 'SERVICE_ENV' in os.environ: 11 | run_env = os.environ['SERVICE_ENV'] 12 | print("Load config [%s]" % run_env) 13 | config_path = '{}/{}.ini'.format(os.path.split(os.path.abspath(__file__))[0], run_env) 14 | if os.path.isfile(config_path): 15 | config = configparser.ConfigParser() 16 | config.read(config_path, encoding='utf-8') 17 | 18 | app_config = dict() 19 | for section in config.sections(): 20 | app_config[section] = dict(config.items(section)) 21 | 22 | app_config["RUN_ENV"] = run_env 23 | return app_config 24 | else: 25 | logging.error("Config not exist") 26 | exit() 27 | -------------------------------------------------------------------------------- /0x08-绅士小站2/Config/openssl.cnf: -------------------------------------------------------------------------------- 1 | # 2 | # OpenSSL example configuration file. 3 | # This is mostly being used for generation of certificate requests. 4 | # 5 | 6 | # Note that you can include other files from the main configuration 7 | # file using the .include directive. 8 | #.include filename 9 | 10 | # This definition stops the following lines choking if HOME isn't 11 | # defined. 12 | HOME = . 13 | 14 | # Extra OBJECT IDENTIFIER info: 15 | #oid_file = $ENV::HOME/.oid 16 | oid_section = new_oids 17 | 18 | # System default 19 | openssl_conf = default_conf 20 | 21 | # To use this configuration file with the "-extfile" option of the 22 | # "openssl x509" utility, name here the section containing the 23 | # X.509v3 extensions to use: 24 | # extensions = 25 | # (Alternatively, use a configuration file that has only 26 | # X.509v3 extensions in its main [= default] section.) 27 | 28 | [ new_oids ] 29 | 30 | # We can add new OIDs in here for use by 'ca', 'req' and 'ts'. 31 | # Add a simple OID like this: 32 | # testoid1=1.2.3.4 33 | # Or use config file substitution like this: 34 | # testoid2=${testoid1}.5.6 35 | 36 | # Policies used by the TSA examples. 37 | tsa_policy1 = 1.2.3.4.1 38 | tsa_policy2 = 1.2.3.4.5.6 39 | tsa_policy3 = 1.2.3.4.5.7 40 | 41 | #################################################################### 42 | [ ca ] 43 | default_ca = CA_default # The default ca section 44 | 45 | #################################################################### 46 | [ CA_default ] 47 | 48 | dir = ./demoCA # Where everything is kept 49 | certs = $dir/certs # Where the issued certs are kept 50 | crl_dir = $dir/crl # Where the issued crl are kept 51 | database = $dir/index.txt # database index file. 52 | #unique_subject = no # Set to 'no' to allow creation of 53 | # several certs with same subject. 54 | new_certs_dir = $dir/newcerts # default place for new certs. 55 | 56 | certificate = $dir/cacert.pem # The CA certificate 57 | serial = $dir/serial # The current serial number 58 | crlnumber = $dir/crlnumber # the current crl number 59 | # must be commented out to leave a V1 CRL 60 | crl = $dir/crl.pem # The current CRL 61 | private_key = $dir/private/cakey.pem# The private key 62 | 63 | x509_extensions = usr_cert # The extensions to add to the cert 64 | 65 | # Comment out the following two lines for the "traditional" 66 | # (and highly broken) format. 67 | name_opt = ca_default # Subject Name options 68 | cert_opt = ca_default # Certificate field options 69 | 70 | # Extension copying option: use with caution. 71 | # copy_extensions = copy 72 | 73 | # Extensions to add to a CRL. Note: Netscape communicator chokes on V2 CRLs 74 | # so this is commented out by default to leave a V1 CRL. 75 | # crlnumber must also be commented out to leave a V1 CRL. 76 | # crl_extensions = crl_ext 77 | 78 | default_days = 365 # how long to certify for 79 | default_crl_days= 30 # how long before next CRL 80 | default_md = default # use public key default MD 81 | preserve = no # keep passed DN ordering 82 | 83 | # A few difference way of specifying how similar the request should look 84 | # For type CA, the listed attributes must be the same, and the optional 85 | # and supplied fields are just that :-) 86 | policy = policy_match 87 | 88 | # For the CA policy 89 | [ policy_match ] 90 | countryName = match 91 | stateOrProvinceName = match 92 | organizationName = match 93 | organizationalUnitName = optional 94 | commonName = supplied 95 | emailAddress = optional 96 | 97 | # For the 'anything' policy 98 | # At this point in time, you must list all acceptable 'object' 99 | # types. 100 | [ policy_anything ] 101 | countryName = optional 102 | stateOrProvinceName = optional 103 | localityName = optional 104 | organizationName = optional 105 | organizationalUnitName = optional 106 | commonName = supplied 107 | emailAddress = optional 108 | 109 | #################################################################### 110 | [ req ] 111 | default_bits = 2048 112 | default_keyfile = privkey.pem 113 | distinguished_name = req_distinguished_name 114 | attributes = req_attributes 115 | x509_extensions = v3_ca # The extensions to add to the self signed cert 116 | 117 | # Passwords for private keys if not present they will be prompted for 118 | # input_password = secret 119 | # output_password = secret 120 | 121 | # This sets a mask for permitted string types. There are several options. 122 | # default: PrintableString, T61String, BMPString. 123 | # pkix : PrintableString, BMPString (PKIX recommendation before 2004) 124 | # utf8only: only UTF8Strings (PKIX recommendation after 2004). 125 | # nombstr : PrintableString, T61String (no BMPStrings or UTF8Strings). 126 | # MASK:XXXX a literal mask value. 127 | # WARNING: ancient versions of Netscape crash on BMPStrings or UTF8Strings. 128 | string_mask = utf8only 129 | 130 | # req_extensions = v3_req # The extensions to add to a certificate request 131 | 132 | [ req_distinguished_name ] 133 | countryName = Country Name (2 letter code) 134 | countryName_default = AU 135 | countryName_min = 2 136 | countryName_max = 2 137 | 138 | stateOrProvinceName = State or Province Name (full name) 139 | stateOrProvinceName_default = Some-State 140 | 141 | localityName = Locality Name (eg, city) 142 | 143 | 0.organizationName = Organization Name (eg, company) 144 | 0.organizationName_default = Internet Widgits Pty Ltd 145 | 146 | # we can do this but it is not needed normally :-) 147 | #1.organizationName = Second Organization Name (eg, company) 148 | #1.organizationName_default = World Wide Web Pty Ltd 149 | 150 | organizationalUnitName = Organizational Unit Name (eg, section) 151 | #organizationalUnitName_default = 152 | 153 | commonName = Common Name (e.g. server FQDN or YOUR name) 154 | commonName_max = 64 155 | 156 | emailAddress = Email Address 157 | emailAddress_max = 64 158 | 159 | # SET-ex3 = SET extension number 3 160 | 161 | [ req_attributes ] 162 | challengePassword = A challenge password 163 | challengePassword_min = 4 164 | challengePassword_max = 20 165 | 166 | unstructuredName = An optional company name 167 | 168 | [ usr_cert ] 169 | 170 | # These extensions are added when 'ca' signs a request. 171 | 172 | # This goes against PKIX guidelines but some CAs do it and some software 173 | # requires this to avoid interpreting an end user certificate as a CA. 174 | 175 | basicConstraints=CA:FALSE 176 | 177 | # Here are some examples of the usage of nsCertType. If it is omitted 178 | # the certificate can be used for anything *except* object signing. 179 | 180 | # This is OK for an SSL server. 181 | # nsCertType = server 182 | 183 | # For an object signing certificate this would be used. 184 | # nsCertType = objsign 185 | 186 | # For normal client use this is typical 187 | # nsCertType = client, email 188 | 189 | # and for everything including object signing: 190 | # nsCertType = client, email, objsign 191 | 192 | # This is typical in keyUsage for a client certificate. 193 | # keyUsage = nonRepudiation, digitalSignature, keyEncipherment 194 | 195 | # This will be displayed in Netscape's comment listbox. 196 | nsComment = "OpenSSL Generated Certificate" 197 | 198 | # PKIX recommendations harmless if included in all certificates. 199 | subjectKeyIdentifier=hash 200 | authorityKeyIdentifier=keyid,issuer 201 | 202 | # This stuff is for subjectAltName and issuerAltname. 203 | # Import the email address. 204 | # subjectAltName=email:copy 205 | # An alternative to produce certificates that aren't 206 | # deprecated according to PKIX. 207 | # subjectAltName=email:move 208 | 209 | # Copy subject details 210 | # issuerAltName=issuer:copy 211 | 212 | #nsCaRevocationUrl = http://www.domain.dom/ca-crl.pem 213 | #nsBaseUrl 214 | #nsRevocationUrl 215 | #nsRenewalUrl 216 | #nsCaPolicyUrl 217 | #nsSslServerName 218 | 219 | # This is required for TSA certificates. 220 | # extendedKeyUsage = critical,timeStamping 221 | 222 | [ v3_req ] 223 | 224 | # Extensions to add to a certificate request 225 | 226 | basicConstraints = CA:FALSE 227 | keyUsage = nonRepudiation, digitalSignature, keyEncipherment 228 | 229 | [ v3_ca ] 230 | 231 | 232 | # Extensions for a typical CA 233 | 234 | 235 | # PKIX recommendation. 236 | 237 | subjectKeyIdentifier=hash 238 | 239 | authorityKeyIdentifier=keyid:always,issuer 240 | 241 | basicConstraints = critical,CA:true 242 | 243 | # Key usage: this is typical for a CA certificate. However since it will 244 | # prevent it being used as an test self-signed certificate it is best 245 | # left out by default. 246 | # keyUsage = cRLSign, keyCertSign 247 | 248 | # Some might want this also 249 | # nsCertType = sslCA, emailCA 250 | 251 | # Include email address in subject alt name: another PKIX recommendation 252 | # subjectAltName=email:copy 253 | # Copy issuer details 254 | # issuerAltName=issuer:copy 255 | 256 | # DER hex encoding of an extension: beware experts only! 257 | # obj=DER:02:03 258 | # Where 'obj' is a standard or added object 259 | # You can even override a supported extension: 260 | # basicConstraints= critical, DER:30:03:01:01:FF 261 | 262 | [ crl_ext ] 263 | 264 | # CRL extensions. 265 | # Only issuerAltName and authorityKeyIdentifier make any sense in a CRL. 266 | 267 | # issuerAltName=issuer:copy 268 | authorityKeyIdentifier=keyid:always 269 | 270 | [ proxy_cert_ext ] 271 | # These extensions should be added when creating a proxy certificate 272 | 273 | # This goes against PKIX guidelines but some CAs do it and some software 274 | # requires this to avoid interpreting an end user certificate as a CA. 275 | 276 | basicConstraints=CA:FALSE 277 | 278 | # Here are some examples of the usage of nsCertType. If it is omitted 279 | # the certificate can be used for anything *except* object signing. 280 | 281 | # This is OK for an SSL server. 282 | # nsCertType = server 283 | 284 | # For an object signing certificate this would be used. 285 | # nsCertType = objsign 286 | 287 | # For normal client use this is typical 288 | # nsCertType = client, email 289 | 290 | # and for everything including object signing: 291 | # nsCertType = client, email, objsign 292 | 293 | # This is typical in keyUsage for a client certificate. 294 | # keyUsage = nonRepudiation, digitalSignature, keyEncipherment 295 | 296 | # This will be displayed in Netscape's comment listbox. 297 | nsComment = "OpenSSL Generated Certificate" 298 | 299 | # PKIX recommendations harmless if included in all certificates. 300 | subjectKeyIdentifier=hash 301 | authorityKeyIdentifier=keyid,issuer 302 | 303 | # This stuff is for subjectAltName and issuerAltname. 304 | # Import the email address. 305 | # subjectAltName=email:copy 306 | # An alternative to produce certificates that aren't 307 | # deprecated according to PKIX. 308 | # subjectAltName=email:move 309 | 310 | # Copy subject details 311 | # issuerAltName=issuer:copy 312 | 313 | #nsCaRevocationUrl = http://www.domain.dom/ca-crl.pem 314 | #nsBaseUrl 315 | #nsRevocationUrl 316 | #nsRenewalUrl 317 | #nsCaPolicyUrl 318 | #nsSslServerName 319 | 320 | # This really needs to be in place for it to be a proxy certificate. 321 | proxyCertInfo=critical,language:id-ppl-anyLanguage,pathlen:3,policy:foo 322 | 323 | #################################################################### 324 | [ tsa ] 325 | 326 | default_tsa = tsa_config1 # the default TSA section 327 | 328 | [ tsa_config1 ] 329 | 330 | # These are used by the TSA reply generation only. 331 | dir = ./demoCA # TSA root directory 332 | serial = $dir/tsaserial # The current serial number (mandatory) 333 | crypto_device = builtin # OpenSSL engine to use for signing 334 | signer_cert = $dir/tsacert.pem # The TSA signing certificate 335 | # (optional) 336 | certs = $dir/cacert.pem # Certificate chain to include in reply 337 | # (optional) 338 | signer_key = $dir/private/tsakey.pem # The TSA private key (optional) 339 | signer_digest = sha256 # Signing digest to use. (Optional) 340 | default_policy = tsa_policy1 # Policy if request did not specify it 341 | # (optional) 342 | other_policies = tsa_policy2, tsa_policy3 # acceptable policies (optional) 343 | digests = sha1, sha256, sha384, sha512 # Acceptable message digests (mandatory) 344 | accuracy = secs:1, millisecs:500, microsecs:100 # (optional) 345 | clock_precision_digits = 0 # number of digits after dot. (optional) 346 | ordering = yes # Is ordering defined for timestamps? 347 | # (optional, default: no) 348 | tsa_name = yes # Must the TSA name be included in the reply? 349 | # (optional, default: no) 350 | ess_cert_id_chain = no # Must the ESS cert id chain be included? 351 | # (optional, default: no) 352 | ess_cert_id_alg = sha1 # algorithm to compute certificate 353 | # identifier (optional, default: sha1) 354 | [default_conf] 355 | ssl_conf = ssl_sect 356 | 357 | [ssl_sect] 358 | system_default = system_default_sect 359 | 360 | [system_default_sect] 361 | MinProtocol = TLSv1.0 362 | CipherString = DEFAULT@SECLEVEL=1 363 | -------------------------------------------------------------------------------- /0x08-绅士小站2/Kit/__init__.py: -------------------------------------------------------------------------------- 1 | from .kit import * 2 | from .chrome import env_check 3 | from .chrome import run_browser 4 | -------------------------------------------------------------------------------- /0x08-绅士小站2/Kit/chrome.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | import os 3 | import re 4 | import Kit 5 | import stat 6 | import zipfile 7 | import requests 8 | import subprocess 9 | from selenium import webdriver 10 | 11 | # 引入Windows特定库 12 | if Kit.run_platform() == "windows": 13 | import winreg 14 | 15 | # 全局变量 16 | version_re = re.compile(r'^[1-9]\d*\.\d*.\d*') 17 | 18 | 19 | def run_browser(driver_path, headless="Yes"): 20 | driver_path = os.path.abspath(driver_path) + "/chromedriver" 21 | option = webdriver.ChromeOptions() 22 | if headless == "Yes": 23 | option.add_argument('--headless') 24 | option.add_argument('--no-sandbox') 25 | option.add_argument('--disable-gpu') 26 | browser = webdriver.Chrome(driver_path, options=option) 27 | return browser 28 | 29 | 30 | def env_check(cache_path): 31 | print("[INFO]", "Check chrome driver env...") 32 | cache_path = os.path.abspath(cache_path) 33 | 34 | chrome_version = get_chrome_version() 35 | driver_version = get_driver_version(cache_path) 36 | 37 | if chrome_version == "0": 38 | print("[INFO]", "Please install chrome browser") 39 | return False 40 | 41 | if driver_version == "0": 42 | # Download driver 43 | print("[INFO]", "Download chrome driver") 44 | return download_driver(chrome_version, cache_path) 45 | 46 | if version_re.findall(chrome_version)[0] != version_re.findall(driver_version)[0]: 47 | # Update driver 48 | print("[INFO]", "Update chrome driver") 49 | return download_driver(chrome_version, cache_path) 50 | 51 | print("[INFO]", "Chrome driver check pass") 52 | return True # Check success 53 | 54 | 55 | def get_chrome_version(): 56 | print("[INFO]", "Check chrome version...") 57 | if Kit.run_platform() == "windows": 58 | try: 59 | # 从注册表中获得版本号 60 | key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, r"Software\Google\Chrome\BLBeacon") 61 | version, _ = winreg.QueryValueEx(key, "version") 62 | 63 | print("[INFO]", "Current chrome Version: {}".format(version)) # 这步打印会在命令行窗口显示 64 | return version 65 | except WindowsError as e: 66 | print("[INFO]", "Check chrome failed:{}".format(e)) 67 | return "0" 68 | else: 69 | try: 70 | cmd = r"google-chrome-stable --version" 71 | out, err = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate() 72 | out = out.decode("utf-8") 73 | version = out.split(" ")[2] # 拆分回显字符串,获取版本号 74 | print("[INFO]", "Current chrome Version:{}".format(version)) 75 | return version 76 | except WindowsError as e: 77 | print("[INFO]", "Check chrome failed:{}".format(e)) 78 | return "0" 79 | 80 | 81 | def get_driver_version(driver_path): 82 | print("[INFO]", "Check driver version...") 83 | if Kit.run_platform() == "windows": 84 | try: 85 | # 执行cmd命令并接收命令回显 86 | cmd = r'"{}/chromedriver" --version'.format(driver_path) 87 | out, err = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate() 88 | out = out.decode("utf-8") 89 | version = out.split(" ")[1] # 拆分回显字符串,获取版本号 90 | print("[INFO]", "Current driver Version:{}".format(version)) 91 | return version 92 | except IndexError as e: 93 | print("[INFO]", "Check driver failed:{}".format(e)) 94 | return "0" 95 | else: 96 | try: 97 | # 执行cmd命令并接收命令回显 98 | cmd = r"{}/chromedriver --version".format(driver_path) 99 | out, err = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate() 100 | out = out.decode("utf-8") 101 | version = out.split(" ")[1] # 拆分回显字符串,获取版本号 102 | print("[INFO]", "Current driver Version:{}".format(version)) 103 | return version 104 | except IndexError as e: 105 | print("[INFO]", "Check driver failed:{}".format(e)) 106 | return "0" 107 | 108 | 109 | def download_driver(version, driver_path): 110 | print("[INFO]", "Download driver on", Kit.run_platform()) 111 | driver_path = os.path.abspath(driver_path) 112 | 113 | # 获取淘宝镜像列表 114 | http_res = requests.get("http://npm.taobao.org/mirrors/chromedriver") 115 | mirror_list = re.findall(r'(.*)', http_res.text) 116 | 117 | # 检索驱动版本列表 118 | url_path = "" 119 | version_prefix = version_re.findall(version)[0] 120 | for mirrors in mirror_list: 121 | if mirrors[1].startswith(version_prefix): 122 | url_path = mirrors[0] 123 | 124 | print("[INFO]", "Download driver version:", url_path.split("/")[-1]) 125 | if Kit.run_platform() == "windows": 126 | file_url = "http://npm.taobao.org/{}/chromedriver_win32.zip".format(url_path) 127 | elif Kit.run_platform() == "linux": 128 | file_url = "http://npm.taobao.org/{}/chromedriver_linux64.zip".format(url_path) 129 | elif Kit.run_platform() == "darwin": 130 | file_url = "http://npm.taobao.org/{}/chromedriver_mac64.zip".format(url_path) 131 | else: 132 | print("[ERR]", "Not found chrome driver") 133 | return False 134 | 135 | print("[INFO]", "Download driver url:", file_url) 136 | driver_res = requests.get(file_url) 137 | 138 | # 写入压缩文件 139 | zip_path = driver_path + "/chromedriver.zip" 140 | zip_file = open(zip_path, "wb") 141 | zip_file.write(driver_res.content) 142 | zip_file.close() 143 | 144 | # 下载完成后解压 145 | zip_file = zipfile.ZipFile(zip_path, "r") 146 | for fileM in zip_file.namelist(): 147 | zip_file.extract(fileM, os.path.dirname(zip_path)) 148 | zip_file.close() 149 | 150 | # 删除残留文件 151 | os.remove(zip_path) 152 | 153 | # 授予执行权限 154 | if Kit.run_platform() == "linux": 155 | os.chmod(driver_path + "/chromedriver", stat.S_IXGRP) 156 | 157 | print("[INFO]", "Download chrome driver finish") 158 | return True 159 | -------------------------------------------------------------------------------- /0x08-绅士小站2/Kit/kit.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | import os 3 | import re 4 | import sys 5 | import uuid 6 | import time 7 | import random 8 | import string 9 | import inspect 10 | import hashlib 11 | import platform 12 | import pymysql 13 | 14 | 15 | # from bs4 import BeautifulSoup 16 | 17 | # MySQL 18 | def mysql_conn(config, db_key): 19 | config[db_key]['port'] = int(config[db_key]['port']) 20 | conn = pymysql.connect(**config[db_key]) 21 | return conn 22 | 23 | 24 | # Print tools 25 | def _print(message, code=None, tag=None, end=None): 26 | if tag is None: 27 | message = '[{}] {}'.format(tag, message) 28 | if code is not None: 29 | message = '\033[0;{}m{}\033[0m'.format(code, message) 30 | print(message, end=end) 31 | 32 | 33 | def print_red(message, tag="ERROR", end=None): 34 | _print(message, 31, tag, end) # 红色 35 | 36 | 37 | def print_green(message, tag="DONE", end='\n'): 38 | _print(message, 32, tag, end) # 绿色 39 | 40 | 41 | def print_yellow(message, tag="WARNING", end='\n'): 42 | _print(message, 33, tag, end) # 黄色 43 | 44 | 45 | def print_blue(message, tag="BEG", end='\n'): 46 | _print(message, 34, tag, end) # 深蓝色 47 | 48 | 49 | def print_purple(message, tag="MID", end='\n'): 50 | _print(message, 35, tag, end) # 紫色 51 | 52 | 53 | def print_azure(message, tag="END", end='\n'): 54 | _print(message, 36, tag, end) # 浅蓝色 55 | 56 | 57 | def print_white(message, tag="INFO", end='\n'): 58 | _print(message, 37, tag, end) # 白色 59 | 60 | 61 | def print_none(message, tag="DEBUG", end='\n'): 62 | _print(message, None, tag, end) # 默认 63 | 64 | 65 | def process_bar(now, total, attach=''): 66 | # 在窗口底部动态显示进度条 67 | rate = now / total 68 | rate_num = int(rate * 100) 69 | bar_length = int(rate_num / 2) 70 | if rate_num == 100: 71 | bar = 'Pid:[%5d]: %s' % (os.getpid(), attach.center(10, " ")) 72 | bar = '\r' + bar[0:40] 73 | bar += '%s>%d%%\n' % ('=' * bar_length, rate_num) 74 | else: 75 | bar = 'Pid:[%5d]: %s' % (os.getpid(), attach.center(10, " ")) 76 | bar = '\r' + bar[0:40] 77 | bar += '%s>%d%%' % ('=' * bar_length, rate_num) 78 | sys.stdout.write(bar) 79 | sys.stdout.flush() 80 | 81 | 82 | # Time tools 83 | 84 | def unix_time(unit=1): 85 | return int(time.time() * unit) 86 | 87 | 88 | def str_time(pattern='%Y-%m-%d %H:%M:%S', timing=None): 89 | if timing is None: 90 | timing = time.time() 91 | return time.strftime(pattern, time.localtime(timing)) 92 | 93 | 94 | def format_time(time_obj, pattern='%Y-%m-%d %H:%M:%S'): 95 | return time.strftime(pattern, time_obj) 96 | 97 | 98 | def datetime2unix(timing): 99 | return int(time.mktime(timing.timetuple())) 100 | 101 | 102 | def timestamp2unix(time_string, pattern='%Y-%m-%d %H:%M:%S'): 103 | time_array = time.strptime(time_string, pattern) 104 | return int(time.mktime(time_array)) 105 | 106 | 107 | def unix2timestamp(u_time, pattern='%Y-%m-%d %H:%M:%S'): 108 | return time.strftime(pattern, time.localtime(u_time)) 109 | 110 | 111 | # Calc tools 112 | 113 | def func_name(fa=1): 114 | return inspect.stack()[fa][3] 115 | 116 | 117 | def random_code(): 118 | return str(uuid.uuid1()).split('-')[0] 119 | 120 | 121 | def random_string(length, chars=string.digits + string.ascii_letters): 122 | return ''.join(random.choice(chars) for _ in range(length)) 123 | 124 | 125 | def calc_sha1(seed): 126 | seed = str(seed).encode('utf-8') 127 | sha1 = hashlib.sha1() 128 | sha1.update(seed) 129 | return sha1.hexdigest() 130 | 131 | 132 | def calc_md5(seed): 133 | seed = str(seed).encode('utf-8') 134 | md5 = hashlib.md5() 135 | md5.update(seed) 136 | return md5.hexdigest() 137 | 138 | 139 | # def parse_xml(data): 140 | # xml = re.sub(r'', lambda m: m.group(1), data) 141 | # xml = BeautifulSoup(xml, 'lxml') 142 | # xml = xml.html.body.xml 143 | # return xml 144 | 145 | 146 | def cpu_core(): 147 | if run_platform() == "windows": 148 | return int(os.popen("echo %NUMBER_OF_PROCESSORS%").read()) 149 | elif run_platform() == "linux": 150 | return int(os.popen(r"cat /proc/cpuinfo | grep 'cpu cores' | uniq | awk '{print $4}'").read()) 151 | else: 152 | return 0 153 | 154 | 155 | def run_platform(): 156 | # windows / linux / darwin 157 | return platform.system().lower() 158 | 159 | 160 | # File tools 161 | 162 | 163 | def code_dir(): 164 | file = os.path.abspath(__file__) 165 | return os.path.dirname(file) 166 | 167 | 168 | def code_path(): 169 | return os.path.abspath(__file__) 170 | 171 | 172 | def legalize_name(name): 173 | legal_name = re.sub(r"[\/\\\:\*\?\"\<\>\|\s']", '_', name) 174 | legal_name = re.sub(r'[‘’]', '_', legal_name) 175 | if len(legal_name) == 0: 176 | return 'null' 177 | return legal_name 178 | 179 | 180 | def delete_old_file(dir_path, expire_time): 181 | time_now = unix_time() 182 | dir_path = os.path.abspath(dir_path) 183 | for file in os.listdir(dir_path): 184 | file_path = '{}/{}'.format(dir_path, file) 185 | creat_time = os.path.getctime(file_path) 186 | if time_now > creat_time + expire_time: 187 | os.remove(file_path) 188 | 189 | 190 | # Network tools 191 | 192 | def parse_cookie(cookies): 193 | if cookies == "": 194 | return {} 195 | cookie_dict = {} 196 | for line in cookies.split(';'): 197 | name, value = line.strip().split('=', 1) 198 | cookie_dict[name] = value 199 | return cookie_dict 200 | 201 | 202 | def random_ip(model="all"): 203 | if model == "A": 204 | return "%d.%d.%d.%d" % (random.randint(1, 126), random.randint(1, 254), 205 | random.randint(1, 254), random.randint(1, 254)) 206 | elif model == "B": 207 | return "%d.%d.%d.%d" % (random.randint(128, 191), random.randint(1, 254), 208 | random.randint(1, 254), random.randint(1, 254)) 209 | elif model == "C": 210 | return "%d.%d.%d.%d" % (random.randint(192, 223), random.randint(1, 254), 211 | random.randint(1, 254), random.randint(1, 254)) 212 | else: 213 | return "%d.%d.%d.%d" % (random.randint(1, 254), random.randint(1, 254), 214 | random.randint(1, 254), random.randint(1, 254)) 215 | -------------------------------------------------------------------------------- /0x08-绅士小站2/Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | url = "https://pypi.org/simple" 3 | verify_ssl = true 4 | name = "pypi" 5 | 6 | [packages] 7 | selenium = "*" 8 | requests = "*" 9 | pymysql = "*" 10 | 11 | [dev-packages] 12 | 13 | [requires] 14 | python_version = "3.8" 15 | -------------------------------------------------------------------------------- /0x08-绅士小站2/Pipfile.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_meta": { 3 | "hash": { 4 | "sha256": "1a52d96a1ef1b2de27506c7953d1206472f1a90be26e37fcc2bc3acf0adbe8f4" 5 | }, 6 | "pipfile-spec": 6, 7 | "requires": { 8 | "python_version": "3.8" 9 | }, 10 | "sources": [ 11 | { 12 | "name": "pypi", 13 | "url": "https://pypi.org/simple", 14 | "verify_ssl": true 15 | } 16 | ] 17 | }, 18 | "default": { 19 | "certifi": { 20 | "hashes": [ 21 | "sha256:2bbf76fd432960138b3ef6dda3dde0544f27cbf8546c458e60baf371917ba9ee", 22 | "sha256:50b1e4f8446b06f41be7dd6338db18e0990601dce795c2b1686458aa7e8fa7d8" 23 | ], 24 | "version": "==2021.5.30" 25 | }, 26 | "chardet": { 27 | "hashes": [ 28 | "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa", 29 | "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5" 30 | ], 31 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", 32 | "version": "==4.0.0" 33 | }, 34 | "idna": { 35 | "hashes": [ 36 | "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6", 37 | "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0" 38 | ], 39 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 40 | "version": "==2.10" 41 | }, 42 | "pymysql": { 43 | "hashes": [ 44 | "sha256:41fc3a0c5013d5f039639442321185532e3e2c8924687abe6537de157d403641", 45 | "sha256:816927a350f38d56072aeca5dfb10221fe1dc653745853d30a216637f5d7ad36" 46 | ], 47 | "index": "pypi", 48 | "version": "==1.0.2" 49 | }, 50 | "requests": { 51 | "hashes": [ 52 | "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804", 53 | "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e" 54 | ], 55 | "index": "pypi", 56 | "version": "==2.25.1" 57 | }, 58 | "selenium": { 59 | "hashes": [ 60 | "sha256:2d7131d7bc5a5b99a2d9b04aaf2612c411b03b8ca1b1ee8d3de5845a9be2cb3c", 61 | "sha256:deaf32b60ad91a4611b98d8002757f29e6f2c2d5fcaf202e1c9ad06d6772300d" 62 | ], 63 | "index": "pypi", 64 | "version": "==3.141.0" 65 | }, 66 | "urllib3": { 67 | "hashes": [ 68 | "sha256:753a0374df26658f99d826cfe40394a686d05985786d946fbe4165b5148f5a7c", 69 | "sha256:a7acd0977125325f516bda9735fa7142b909a8d01e8b2e4c8108d0984e6e0098" 70 | ], 71 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'", 72 | "version": "==1.26.5" 73 | } 74 | }, 75 | "develop": {} 76 | } 77 | -------------------------------------------------------------------------------- /0x08-绅士小站2/main.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | import os 3 | import re 4 | import Kit 5 | import uuid 6 | import Config 7 | import urllib3 8 | import pymysql 9 | import requests 10 | import subprocess 11 | import urllib.error 12 | import urllib.request 13 | from bs4 import BeautifulSoup 14 | from urllib.parse import quote 15 | 16 | urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) 17 | 18 | 19 | def main(): 20 | config = Config.get_config() 21 | print("[INFO]", "Start at", Kit.str_time()) 22 | 23 | # Fetch archives 24 | session = requests.Session() 25 | session.headers = { 26 | "User-Agent": config['BASE']['user_agent'], 27 | "Connection": "keep-alive", 28 | "Accept": "*/*", 29 | "Accept-Encoding": "gzip, deflate", 30 | "Accept-Language": "zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7" 31 | } 32 | session.cookies.update({"age_gate": "18"}) 33 | 34 | conn = Kit.mysql_conn(config, "MYSQL") 35 | cursor = conn.cursor(pymysql.cursors.DictCursor) 36 | 37 | for pid in range(1, 2760): 38 | sql = "SELECT * FROM `gca_tw` WHERE `id`=%s" 39 | cursor.execute(sql, pid) 40 | record = cursor.fetchone() 41 | if record is not None and record["tag"] in ["202", "404"]: 42 | continue 43 | 44 | if record is not None and record["download"] == "Yes": 45 | continue 46 | 47 | try: 48 | fetch_page(config, conn, session, pid) 49 | except Exception as e: 50 | Kit.print_red("Runtime Error: {}".format(e)) 51 | raise e 52 | 53 | 54 | def fetch_page(config, conn, session, pid): 55 | # Fetch info data 56 | page_url = "https://ca.gca.tw/{}.html".format(pid) 57 | print("[INFO]", "Fetch page {} ... ".format(page_url), end="") 58 | 59 | # Get page data 60 | res = session.get(page_url) 61 | 62 | if res.status_code == 404: 63 | Kit.print_red("ERR-404") 64 | error_record(conn, pid, 404) 65 | return False 66 | Kit.print_green("SUCCESS") 67 | 68 | soup = BeautifulSoup(res.text, "lxml") 69 | title = soup.find(rel="bookmark") 70 | if title is None: 71 | title = "None-{}".format(uuid.uuid1()) 72 | else: 73 | title = title.string 74 | print("Page title:", title) 75 | 76 | # Submit password 77 | payload = { 78 | "post_password": title, 79 | "Submit": "提交" 80 | } 81 | session.post("https://ca.gca.tw/wp-login.php?action=postpass", data=payload) 82 | 83 | # Re-get page content 84 | res = session.get(page_url) 85 | 86 | soup = BeautifulSoup(res.text, "lxml") 87 | content = soup.find(class_="entry-content") 88 | 89 | sql_data = { 90 | "pid": pid, 91 | "url": page_url, 92 | "tag": "video", 93 | "title": title, 94 | "cover": "", 95 | "video": "", 96 | "download": "No", 97 | "raw_data": res.text 98 | } 99 | sql = "REPLACE INTO `gca_tw`(id,url,tag,title,cover,video,download,raw_data)VALUES(%s,%s,%s,%s,%s,%s,%s,%s)" 100 | cursor = conn.cursor() 101 | cursor.execute(sql, args=list(sql_data.values())) 102 | conn.commit() 103 | 104 | # Download image 105 | image_list = content.find_all("img") 106 | if image_list is not None: 107 | for index, image in enumerate(image_list): 108 | image_url = "https://ca.gca.tw/{}".format(image["src"]) 109 | image_path = "{}/Image/{}-{}.jpg".format(config["BASE"]["download_path"], legalize_name(title), index) 110 | try: 111 | urllib.request.urlretrieve(quote(image_url, safe='/:?='), filename=image_path) # Images 112 | except urllib.error.HTTPError: 113 | continue 114 | 115 | # Download video 116 | video_box = content.find("video") 117 | if video_box is not None: 118 | 119 | if video_box["poster"].find("ca.gca.tw") != -1: 120 | cover_url = video_box["poster"] 121 | else: 122 | cover_url = "https://ca.gca.tw/{}".format(video_box["poster"]) 123 | 124 | if "src" in video_box.attrs.keys(): 125 | video_url = video_box["src"] 126 | else: 127 | video_url = video_box.find("source")["src"] 128 | 129 | video_path = os.path.abspath("{}/{}".format(config["BASE"]["download_path"], legalize_name(title))) 130 | if os.path.exists(video_path) is False: 131 | os.mkdir(video_path) 132 | 133 | urllib.request.urlretrieve(quote(cover_url, safe='/:?='), filename="{}/cover.jpg".format(video_path)) # Image 134 | aria2c_pull(config, page_url, video_path, "{}.mp4".format(legalize_name(title)), [video_url], True) # Video 135 | 136 | sql = "UPDATE `gca_tw` SET `cover`=%s,`video`=%s WHERE `id`=%s" 137 | cursor.execute(sql, args=[cover_url, video_url, pid]) 138 | 139 | sql = "UPDATE `gca_tw` SET `download`='Yes' WHERE `id`=%s" 140 | cursor.execute(sql, args=[pid]) 141 | conn.commit() 142 | 143 | 144 | def error_record(conn, pid, status): 145 | cursor = conn.cursor() 146 | sql = "REPLACE INTO `gca_tw`(id,url,tag,title,cover,video,download,raw_data)VALUES(%s,%s,%s,%s,%s,%s,%s,%s)" 147 | cursor.execute(sql, args=[pid, "https://ca.gca.tw/{}.html".format(pid), status, "无法访问", "", "", "", ""]) 148 | conn.commit() 149 | 150 | 151 | def aria2c_pull(config, page, path, name, url_list, show_process=False): 152 | # 设置输出信息 153 | if show_process: 154 | out_pipe = None 155 | else: 156 | out_pipe = subprocess.PIPE 157 | 158 | # 读取代理信息 159 | proxies = '' 160 | proxies += ' --http-proxy="{}"'.format(config["PROXY"]["http"]) if config["PROXY"]["http"] is not None else "" 161 | proxies += ' --https-proxy="{}"'.format(config["PROXY"]["https"]) if config["PROXY"]["https"] is not None else "" 162 | 163 | url = '"{}"'.format('" "'.join(url_list)) 164 | shell = 'aria2c -c -k 1M -x {} -d "{}" -o "{}" --referer="{}" {} {}' 165 | shell = shell.format(len(url_list) * 4, path, name, page, proxies, url) 166 | process = subprocess.Popen(shell, stdout=out_pipe, stderr=out_pipe, shell=True) 167 | process.wait() 168 | 169 | 170 | def legalize_name(name): 171 | legal_name = re.sub(r"[\/\\\:\*\?\"\<\>\|\s']", '_', name) 172 | legal_name = re.sub(r'[‘’]', '_', legal_name) 173 | if len(legal_name) == 0: 174 | return 'null' 175 | return legal_name 176 | 177 | 178 | if __name__ == '__main__': 179 | main() 180 | -------------------------------------------------------------------------------- /0x09-Pixiv插画tag数据导入Eagle/Home.library/images/ffe126de-87b1-915b-a0c3-6032b9343bbb.info/86352163-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WriteCode-ChangeWorld/Tools/096dd8af8b9d1f501187ce62ab38e6be07f1518d/0x09-Pixiv插画tag数据导入Eagle/Home.library/images/ffe126de-87b1-915b-a0c3-6032b9343bbb.info/86352163-1.jpg -------------------------------------------------------------------------------- /0x09-Pixiv插画tag数据导入Eagle/Home.library/images/ffe126de-87b1-915b-a0c3-6032b9343bbb.info/86352163-1_thumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WriteCode-ChangeWorld/Tools/096dd8af8b9d1f501187ce62ab38e6be07f1518d/0x09-Pixiv插画tag数据导入Eagle/Home.library/images/ffe126de-87b1-915b-a0c3-6032b9343bbb.info/86352163-1_thumbnail.png -------------------------------------------------------------------------------- /0x09-Pixiv插画tag数据导入Eagle/Home.library/images/ffe126de-87b1-915b-a0c3-6032b9343bbb.info/metadata.json: -------------------------------------------------------------------------------- 1 | {"id":"ffe126de-87b1-915b-a0c3-6032b9343bbb","name":"86352163-1","size":58711,"ext":"jpg","tags":[],"folders":[],"isDeleted":false,"url":"","annotation":"","modificationTime":1636191425037,"height":420,"width":560,"palettes":[{"color":[214,172,147],"ratio":61.65093537414966},{"color":[61,49,53],"ratio":18.721513605442176},{"color":[250,245,237],"ratio":14.74702380952381},{"color":[179,69,80],"ratio":4.719812925170068},{"color":[215,101,65],"ratio":0.1607142857142857}]} -------------------------------------------------------------------------------- /0x09-Pixiv插画tag数据导入Eagle/Home.library/images/ffea5831-3817-f97f-c375-b5c38ebaa890.info/76315323.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WriteCode-ChangeWorld/Tools/096dd8af8b9d1f501187ce62ab38e6be07f1518d/0x09-Pixiv插画tag数据导入Eagle/Home.library/images/ffea5831-3817-f97f-c375-b5c38ebaa890.info/76315323.jpg -------------------------------------------------------------------------------- /0x09-Pixiv插画tag数据导入Eagle/Home.library/images/ffea5831-3817-f97f-c375-b5c38ebaa890.info/76315323_thumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WriteCode-ChangeWorld/Tools/096dd8af8b9d1f501187ce62ab38e6be07f1518d/0x09-Pixiv插画tag数据导入Eagle/Home.library/images/ffea5831-3817-f97f-c375-b5c38ebaa890.info/76315323_thumbnail.png -------------------------------------------------------------------------------- /0x09-Pixiv插画tag数据导入Eagle/Home.library/images/ffea5831-3817-f97f-c375-b5c38ebaa890.info/metadata.json: -------------------------------------------------------------------------------- 1 | {"id":"ffea5831-3817-f97f-c375-b5c38ebaa890","name":"76315323","size":1383919,"ext":"jpg","tags":[],"folders":[],"isDeleted":false,"url":"","annotation":"","modificationTime":1636191428551,"height":1174,"width":1660,"palettes":[{"color":[236,216,208],"ratio":79.83029801324504},{"color":[76,75,76],"ratio":4.908940397350993},{"color":[208,183,123],"ratio":12.304773730684326},{"color":[155,77,113],"ratio":1.584575055187638},{"color":[190,161,82],"ratio":1.3714128035320088}]} -------------------------------------------------------------------------------- /0x09-Pixiv插画tag数据导入Eagle/Home.library/images/ffeaeaaa-e475-8741-663e-c4a3308c6e3b.info/89193684.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WriteCode-ChangeWorld/Tools/096dd8af8b9d1f501187ce62ab38e6be07f1518d/0x09-Pixiv插画tag数据导入Eagle/Home.library/images/ffeaeaaa-e475-8741-663e-c4a3308c6e3b.info/89193684.jpg -------------------------------------------------------------------------------- /0x09-Pixiv插画tag数据导入Eagle/Home.library/images/ffeaeaaa-e475-8741-663e-c4a3308c6e3b.info/89193684_thumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WriteCode-ChangeWorld/Tools/096dd8af8b9d1f501187ce62ab38e6be07f1518d/0x09-Pixiv插画tag数据导入Eagle/Home.library/images/ffeaeaaa-e475-8741-663e-c4a3308c6e3b.info/89193684_thumbnail.png -------------------------------------------------------------------------------- /0x09-Pixiv插画tag数据导入Eagle/Home.library/images/ffeaeaaa-e475-8741-663e-c4a3308c6e3b.info/metadata.json: -------------------------------------------------------------------------------- 1 | {"id":"ffeaeaaa-e475-8741-663e-c4a3308c6e3b","name":"89193684","size":2191039,"ext":"jpg","tags":[],"folders":[],"isDeleted":false,"url":"","annotation":"","modificationTime":1636191423869,"height":3508,"width":2000,"palettes":[{"color":[240,227,225],"ratio":72.95608860195904},{"color":[66,62,76],"ratio":14.129424532502227},{"color":[168,141,153],"ratio":10.545970614425645},{"color":[134,100,107],"ratio":2.1948742208370438},{"color":[195,138,98],"ratio":0.1736420302760463}]} -------------------------------------------------------------------------------- /0x09-Pixiv插画tag数据导入Eagle/Home.library/images/ffeb50a5-5447-a4fc-cc95-a009fc385fbd.info/84372116.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WriteCode-ChangeWorld/Tools/096dd8af8b9d1f501187ce62ab38e6be07f1518d/0x09-Pixiv插画tag数据导入Eagle/Home.library/images/ffeb50a5-5447-a4fc-cc95-a009fc385fbd.info/84372116.jpg -------------------------------------------------------------------------------- /0x09-Pixiv插画tag数据导入Eagle/Home.library/images/ffeb50a5-5447-a4fc-cc95-a009fc385fbd.info/84372116_thumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WriteCode-ChangeWorld/Tools/096dd8af8b9d1f501187ce62ab38e6be07f1518d/0x09-Pixiv插画tag数据导入Eagle/Home.library/images/ffeb50a5-5447-a4fc-cc95-a009fc385fbd.info/84372116_thumbnail.png -------------------------------------------------------------------------------- /0x09-Pixiv插画tag数据导入Eagle/Home.library/images/ffeb50a5-5447-a4fc-cc95-a009fc385fbd.info/metadata.json: -------------------------------------------------------------------------------- 1 | {"id":"ffeb50a5-5447-a4fc-cc95-a009fc385fbd","name":"84372116","size":1467404,"ext":"jpg","tags":[],"folders":[],"isDeleted":false,"url":"","annotation":"","modificationTime":1636191426487,"height":1400,"width":1900,"palettes":[{"color":[198,194,194],"ratio":70.76635328389831},{"color":[52,51,55],"ratio":11.096398305084746},{"color":[108,70,78],"ratio":7.77443061440678},{"color":[115,166,201],"ratio":9.830177436440678},{"color":[129,109,88],"ratio":0.5326403601694916}]} -------------------------------------------------------------------------------- /0x09-Pixiv插画tag数据导入Eagle/Home.library/images/ffebbbbf-0a8f-732d-b196-e36fa2a12b9e.info/70899400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WriteCode-ChangeWorld/Tools/096dd8af8b9d1f501187ce62ab38e6be07f1518d/0x09-Pixiv插画tag数据导入Eagle/Home.library/images/ffebbbbf-0a8f-732d-b196-e36fa2a12b9e.info/70899400.png -------------------------------------------------------------------------------- /0x09-Pixiv插画tag数据导入Eagle/Home.library/images/ffebbbbf-0a8f-732d-b196-e36fa2a12b9e.info/70899400_thumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WriteCode-ChangeWorld/Tools/096dd8af8b9d1f501187ce62ab38e6be07f1518d/0x09-Pixiv插画tag数据导入Eagle/Home.library/images/ffebbbbf-0a8f-732d-b196-e36fa2a12b9e.info/70899400_thumbnail.png -------------------------------------------------------------------------------- /0x09-Pixiv插画tag数据导入Eagle/Home.library/images/ffebbbbf-0a8f-732d-b196-e36fa2a12b9e.info/metadata.json: -------------------------------------------------------------------------------- 1 | {"id":"ffebbbbf-0a8f-732d-b196-e36fa2a12b9e","name":"70899400","size":1384499,"ext":"png","tags":[],"folders":[],"isDeleted":false,"url":"","annotation":"","modificationTime":1636191429063,"height":1754,"width":1240,"palettes":[{"color":[249,209,179],"ratio":54.89813535911602},{"color":[67,43,47],"ratio":25.211671270718234},{"color":[212,149,101],"ratio":17.17092541436464},{"color":[182,108,72],"ratio":2.2821132596685083},{"color":[122,114,98],"ratio":0.4371546961325967}]} -------------------------------------------------------------------------------- /0x09-Pixiv插画tag数据导入Eagle/Home.library/images/ffebbe70-01e0-4ef0-9060-c9380f4aabc4.info/92016561-0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WriteCode-ChangeWorld/Tools/096dd8af8b9d1f501187ce62ab38e6be07f1518d/0x09-Pixiv插画tag数据导入Eagle/Home.library/images/ffebbe70-01e0-4ef0-9060-c9380f4aabc4.info/92016561-0.jpg -------------------------------------------------------------------------------- /0x09-Pixiv插画tag数据导入Eagle/Home.library/images/ffebbe70-01e0-4ef0-9060-c9380f4aabc4.info/92016561-0_thumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WriteCode-ChangeWorld/Tools/096dd8af8b9d1f501187ce62ab38e6be07f1518d/0x09-Pixiv插画tag数据导入Eagle/Home.library/images/ffebbe70-01e0-4ef0-9060-c9380f4aabc4.info/92016561-0_thumbnail.png -------------------------------------------------------------------------------- /0x09-Pixiv插画tag数据导入Eagle/Home.library/images/ffebbe70-01e0-4ef0-9060-c9380f4aabc4.info/metadata.json: -------------------------------------------------------------------------------- 1 | {"id":"ffebbe70-01e0-4ef0-9060-c9380f4aabc4","name":"92016561-0","size":1170919,"ext":"jpg","tags":[],"folders":[],"isDeleted":false,"url":"","annotation":"","modificationTime":1636191423103,"height":1200,"width":1600,"palettes":[{"color":[231,218,218],"ratio":71.7060546875},{"color":[72,76,90],"ratio":15.440104166666666},{"color":[141,142,157],"ratio":11.693684895833334},{"color":[128,101,109],"ratio":1.0696614583333333},{"color":[161,125,99],"ratio":0.09049479166666666}]} -------------------------------------------------------------------------------- /0x09-Pixiv插画tag数据导入Eagle/Home.library/images/fff07694-c82e-e1b8-c7b3-9c7e4cb5d2a3.info/73190808-0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WriteCode-ChangeWorld/Tools/096dd8af8b9d1f501187ce62ab38e6be07f1518d/0x09-Pixiv插画tag数据导入Eagle/Home.library/images/fff07694-c82e-e1b8-c7b3-9c7e4cb5d2a3.info/73190808-0.jpg -------------------------------------------------------------------------------- /0x09-Pixiv插画tag数据导入Eagle/Home.library/images/fff07694-c82e-e1b8-c7b3-9c7e4cb5d2a3.info/73190808-0_thumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WriteCode-ChangeWorld/Tools/096dd8af8b9d1f501187ce62ab38e6be07f1518d/0x09-Pixiv插画tag数据导入Eagle/Home.library/images/fff07694-c82e-e1b8-c7b3-9c7e4cb5d2a3.info/73190808-0_thumbnail.png -------------------------------------------------------------------------------- /0x09-Pixiv插画tag数据导入Eagle/Home.library/images/fff07694-c82e-e1b8-c7b3-9c7e4cb5d2a3.info/metadata.json: -------------------------------------------------------------------------------- 1 | {"id":"fff07694-c82e-e1b8-c7b3-9c7e4cb5d2a3","name":"73190808-0","size":347574,"ext":"jpg","tags":[],"folders":[],"isDeleted":false,"url":"","annotation":"","modificationTime":1636191428865,"height":1200,"width":882,"palettes":[{"color":[242,217,212],"ratio":85.34138203214697},{"color":[92,100,124],"ratio":1.551377726750861},{"color":[211,142,161],"ratio":10.058481630309988},{"color":[169,78,106],"ratio":1.8477324913892077},{"color":[139,160,98],"ratio":1.201026119402985}]} -------------------------------------------------------------------------------- /0x09-Pixiv插画tag数据导入Eagle/Home.library/images/fff4c7d0-7060-e34d-1dc5-cdb85cb058c9.info/83619652-11.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WriteCode-ChangeWorld/Tools/096dd8af8b9d1f501187ce62ab38e6be07f1518d/0x09-Pixiv插画tag数据导入Eagle/Home.library/images/fff4c7d0-7060-e34d-1dc5-cdb85cb058c9.info/83619652-11.jpg -------------------------------------------------------------------------------- /0x09-Pixiv插画tag数据导入Eagle/Home.library/images/fff4c7d0-7060-e34d-1dc5-cdb85cb058c9.info/83619652-11_thumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WriteCode-ChangeWorld/Tools/096dd8af8b9d1f501187ce62ab38e6be07f1518d/0x09-Pixiv插画tag数据导入Eagle/Home.library/images/fff4c7d0-7060-e34d-1dc5-cdb85cb058c9.info/83619652-11_thumbnail.png -------------------------------------------------------------------------------- /0x09-Pixiv插画tag数据导入Eagle/Home.library/images/fff4c7d0-7060-e34d-1dc5-cdb85cb058c9.info/metadata.json: -------------------------------------------------------------------------------- 1 | {"id":"fff4c7d0-7060-e34d-1dc5-cdb85cb058c9","name":"83619652-11","size":920930,"ext":"jpg","tags":[],"folders":[],"isDeleted":false,"url":"","annotation":"","modificationTime":1636191426753,"height":1912,"width":1080,"palettes":[{"color":[232,216,214],"ratio":67.99205648720212},{"color":[49,51,67],"ratio":9.545316637246248},{"color":[182,107,86],"ratio":7.982265004413063},{"color":[164,127,135],"ratio":11.561258826125332},{"color":[151,82,66],"ratio":2.919103045013239}]} -------------------------------------------------------------------------------- /0x09-Pixiv插画tag数据导入Eagle/Home.library/images/fff5e73d-460c-fbad-e90a-3080c6a7a923.info/88199924.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WriteCode-ChangeWorld/Tools/096dd8af8b9d1f501187ce62ab38e6be07f1518d/0x09-Pixiv插画tag数据导入Eagle/Home.library/images/fff5e73d-460c-fbad-e90a-3080c6a7a923.info/88199924.jpg -------------------------------------------------------------------------------- /0x09-Pixiv插画tag数据导入Eagle/Home.library/images/fff5e73d-460c-fbad-e90a-3080c6a7a923.info/88199924_thumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WriteCode-ChangeWorld/Tools/096dd8af8b9d1f501187ce62ab38e6be07f1518d/0x09-Pixiv插画tag数据导入Eagle/Home.library/images/fff5e73d-460c-fbad-e90a-3080c6a7a923.info/88199924_thumbnail.png -------------------------------------------------------------------------------- /0x09-Pixiv插画tag数据导入Eagle/Home.library/images/fff5e73d-460c-fbad-e90a-3080c6a7a923.info/metadata.json: -------------------------------------------------------------------------------- 1 | {"id":"fff5e73d-460c-fbad-e90a-3080c6a7a923","name":"88199924","size":2718489,"ext":"jpg","tags":[],"folders":[],"isDeleted":false,"url":"","annotation":"","modificationTime":1636191424174,"height":3743,"width":2637,"palettes":[{"color":[91,87,97],"ratio":28.84567731277533},{"color":[245,243,243],"ratio":47.4671324339207},{"color":[42,41,52],"ratio":18.91416574889868},{"color":[130,136,149],"ratio":3.848430616740088},{"color":[187,132,109],"ratio":0.9245938876651982}]} -------------------------------------------------------------------------------- /0x09-Pixiv插画tag数据导入Eagle/Home.library/images/fff75130-4feb-2d23-24bc-1b098faab96e.info/82329785.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WriteCode-ChangeWorld/Tools/096dd8af8b9d1f501187ce62ab38e6be07f1518d/0x09-Pixiv插画tag数据导入Eagle/Home.library/images/fff75130-4feb-2d23-24bc-1b098faab96e.info/82329785.png -------------------------------------------------------------------------------- /0x09-Pixiv插画tag数据导入Eagle/Home.library/images/fff75130-4feb-2d23-24bc-1b098faab96e.info/82329785_thumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WriteCode-ChangeWorld/Tools/096dd8af8b9d1f501187ce62ab38e6be07f1518d/0x09-Pixiv插画tag数据导入Eagle/Home.library/images/fff75130-4feb-2d23-24bc-1b098faab96e.info/82329785_thumbnail.png -------------------------------------------------------------------------------- /0x09-Pixiv插画tag数据导入Eagle/Home.library/images/fff75130-4feb-2d23-24bc-1b098faab96e.info/metadata.json: -------------------------------------------------------------------------------- 1 | {"id":"fff75130-4feb-2d23-24bc-1b098faab96e","name":"82329785","size":1688685,"ext":"png","tags":[],"folders":[],"isDeleted":false,"url":"","annotation":"","modificationTime":1636191427179,"height":2000,"width":1500,"palettes":[{"color":[183,136,117],"ratio":50.92944021101993},{"color":[31,24,41],"ratio":22.69306858147714},{"color":[250,231,216],"ratio":23.539346424384526},{"color":[107,63,54],"ratio":1.936730656506448},{"color":[145,80,59],"ratio":0.9014141266119577}]} -------------------------------------------------------------------------------- /0x09-Pixiv插画tag数据导入Eagle/Home.library/metadata.json: -------------------------------------------------------------------------------- 1 | {"folders":[{"children":[],"iconColor":"yellow","id":"e02bd9a5-e2c8-7f80-5c88-967c49387065","modificationTime":1623600319529,"name":"pixiv","tags":[]},{"children":[],"iconColor":"green","id":"e8a01709-1e28-c7ae-24ed-87a84b69fa9d","modificationTime":1634125750902,"name":"检验集","tags":[]}],"smartFolders":[],"quickAccess":[],"tagsGroups":[],"modificationTime":1634126063780,"applicationVersion":"1.6.2"} -------------------------------------------------------------------------------- /0x09-Pixiv插画tag数据导入Eagle/Home.library/tags.json: -------------------------------------------------------------------------------- 1 | {"historyTags":["巨乳","新泽西"],"starredTags":[]} -------------------------------------------------------------------------------- /0x09-Pixiv插画tag数据导入Eagle/Readme.md: -------------------------------------------------------------------------------- 1 | ## Pixiv插画tag导入Eagle 2 | 3 | --- 4 | 5 | + 偶然间被安利了`Eagle`这样一款图片管理软件,初次使用的时候看到这个tag就觉得大有用处。 6 | + 现在可以使用`pixiv2eagle`为`Eagle`内的`pixiv`插画添加`tag`,从而更好的建造、管理、筛选自己的`pixiv`插画数据库(涩图库) 7 | + 需要魔法使用 8 | 9 | ↓ 如图,筛选一下<柴郡>,全是可爱的猫猫:happy: 10 | (会遗漏部分不是从`pixiv`上下载的插画,因为没有`tag`可筛选) 11 | 12 | ![1.png](./img/1.png) 13 | 14 | + 这里给下数据参考。此处为使用`PixiC`下载`pixiv`个人收藏共`4.6w`张图片(`88G`),导入Eagle后为`98G` (`Eagle`生成缩略图占用了`10G`) 15 | + 脚本默认使用8线程,处理以上数据花费时间 : `52min`(`13:05:34.040~13:57:52.723`) 16 | 17 | 18 | 19 | **功能** 20 | 21 | - [x] 从[PIXIC API](https://github.com/Coder-Sakura/PixiC/wiki)或`pixiv`获取对应插画的tag数据 22 | - [x] 将插画`tag`数据及画师名称导入`Eagle metadata.json` 23 | - [x] 线程池处理任务(默认8线程) 24 | - [x] 执行过程及多线程日志记录 25 | 26 | 27 | 28 | **支持识别的`pixiv`插画文件名称** 29 | 30 | + `86352163.jpg` / `86352163.png` 31 | + `86352163-1.jpg` / `86352163-2.png` 32 | + `86352163_p0.jpg` / `86352163_p1.png` 33 | 34 | 35 | 36 | **一些话** 37 | 38 | 1. 由`PixiC`下载得到的`pixiv`插画,格式为`86352163-1.jpg`或`86352163.jpg`的形式(截图中使用的) 39 | 2. 直接从`pixiv`处下载的插画格式为`86352163_p0.jpg`,其他工具下载的文件请自行命令或在`Pixiv2Eagle.get_pid`处添加识别逻辑 40 | 41 | 42 | 43 | **使用方法** 44 | 45 | 1. 添加插画到`Eagle` 46 | 47 | 选中要加入的图片文件,拖拽到`Eagle`上即可;或从左上角选择路径载入 48 | 49 | ![](./img/0.png) 50 | 51 | 52 | 53 | 2. 复制`Eagle`资源库地址 54 | 55 | > 这个地址就是上面创建的;比如`G:\EagleHome\个人收藏.library` 56 | 57 | 填入`pixiv2eagle.py`中的`EAGLE_HOME_PATH`,如: 58 | 59 | ```python 60 | EAGLE_HOME_PATH = r"your EAGLE_HOME_PATH" 61 | ``` 62 | 63 | 64 | 65 | > 这里提供了一个TEST DIR供测试使用,即`Home.library`目录 66 | 67 | 68 | 69 | 3. 选择获取pixiv插画数据的方式 70 | 71 | **从`PIXIC_API`获取** 72 | 73 | 将你的`PIXIC_API`地址填入到`pixiv2eagle.py`中的`PIXIC_API`,如: 74 | 75 | ```python 76 | # 使用的是get-info API 77 | # 默认操作的是Bookmark表 78 | PIXIC_API = "http://xxx.xx.xxx.xx:1526/api/v2/get-info" 79 | ``` 80 | 81 | 82 | 83 | **从`pixiv`获取** 84 | 85 | 将`PIXIC_API`设置为空即可 86 | 87 | ```python 88 | PIXIC_API = "" 89 | ``` 90 | 91 | 92 | 93 | 4. 开始使用 94 | 95 | ```bash 96 | # 安装依赖 97 | pip install -r requirement.txt 98 | # 运行脚本 99 | python pixiv2eagle.py 100 | ``` 101 | 102 | 103 | 104 | **运行及效果截图** 105 | 106 | 脚本写入tag 107 | 108 | ![](./img/2.png) 109 | 110 | 写入tag后在Eagle中的使用 111 | 112 | 柴郡 113 | 114 | ![](./img/1.png) 115 | 116 | 大凤 117 | 118 | ![](./img/4.png) 119 | 120 | 支持联想tag 121 | 122 | ![](./img/3.png) -------------------------------------------------------------------------------- /0x09-Pixiv插画tag数据导入Eagle/img/0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WriteCode-ChangeWorld/Tools/096dd8af8b9d1f501187ce62ab38e6be07f1518d/0x09-Pixiv插画tag数据导入Eagle/img/0.png -------------------------------------------------------------------------------- /0x09-Pixiv插画tag数据导入Eagle/img/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WriteCode-ChangeWorld/Tools/096dd8af8b9d1f501187ce62ab38e6be07f1518d/0x09-Pixiv插画tag数据导入Eagle/img/1.png -------------------------------------------------------------------------------- /0x09-Pixiv插画tag数据导入Eagle/img/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WriteCode-ChangeWorld/Tools/096dd8af8b9d1f501187ce62ab38e6be07f1518d/0x09-Pixiv插画tag数据导入Eagle/img/2.png -------------------------------------------------------------------------------- /0x09-Pixiv插画tag数据导入Eagle/img/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WriteCode-ChangeWorld/Tools/096dd8af8b9d1f501187ce62ab38e6be07f1518d/0x09-Pixiv插画tag数据导入Eagle/img/3.png -------------------------------------------------------------------------------- /0x09-Pixiv插画tag数据导入Eagle/img/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WriteCode-ChangeWorld/Tools/096dd8af8b9d1f501187ce62ab38e6be07f1518d/0x09-Pixiv插画tag数据导入Eagle/img/4.png -------------------------------------------------------------------------------- /0x09-Pixiv插画tag数据导入Eagle/pixiv2eagle.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | ''' 3 | @File : pixiv2eagle.py 4 | @Time : 2021/11/04 15:25:48 5 | @Author : Coder-Sakura 6 | @Version : 1.0 7 | @Desc : 将pixiv插画的tag信息写入到Eagle的metadata.json中\ 8 | 使得在Eagle中也能通过标签查找自己喜欢的作品 9 | ''' 10 | 11 | # here put the import lib 12 | import os 13 | import json 14 | import time 15 | import requests 16 | from loguru import logger 17 | # 强制取消警告 18 | from requests.packages.urllib3.exceptions import InsecureRequestWarning 19 | requests.packages.urllib3.disable_warnings(InsecureRequestWarning) 20 | 21 | from thread_pool import ThreadPool,callback 22 | 23 | """ 24 | ========================================================= 25 | 1. 插画图片名称 26 | 可识别的pixiv插画名称包括: 27 | # 图片从PIXIC下载 28 | .jpg 29 | .png 30 | -1.jpg 31 | -2.png 32 | # 直接从PIXIV下载 33 | _p0.jpg 34 | _p1.png 35 | 36 | ========================================================= 37 | 2. tag数据来源 38 | pixiv2eagle.py 提供两种形式以获取 pixiv 插画的 tag 信息 39 | 40 | # 通过PixiC API的形式, PixiC API的部署形式可以参考: 41 | https://github.com/Coder-Sakura/PixiC/wiki 42 | 43 | 部署好之后,先地址填入 PIXIC_API_HOST. 44 | 如: PIXIC_API = http://xxx.xx.xxx.xx:1526/api/v2/get-info 45 | 46 | # 觉得麻烦的朋友直接将 PIXIC_API 留空. 如: PIXIC_API = "" 47 | 48 | ========================================================= 49 | 3. 填入Eagle Home Path 50 | 51 | 使用Eagle创建一个新的资源库时,在你指定的文件夹下会生成 52 | 形如 Home.library的文件夹, 将这个地址填入到EAGLE_HOME_PATH即可 53 | 如: EAGLE_HOME_PATH = r"G:\EagleHome\Home.library" 54 | 55 | """ 56 | 57 | 58 | PIXIC_API = "" 59 | # EAGLE_HOME_PATH = r"G:\EagleHome\个人收藏.library" 60 | # TEST DIR -> Home.library 61 | EAGLE_HOME_PATH = r"" 62 | 63 | log_path = os.path.split(os.path.abspath(__file__))[0] 64 | # 日志写入 65 | logger.add( 66 | os.path.join(log_path, "{time}.log"), 67 | encoding="utf-8", 68 | enqueue=True, 69 | ) 70 | TOOL = ThreadPool(8) 71 | 72 | class Pixiv2Eagle: 73 | # 计数 74 | count = 0 75 | # 识别成功 76 | _count = 0 77 | # 无法识别 78 | un_count = 0 79 | # 写入成功 80 | succees_count = 0 81 | # 已写入 82 | pass_count = 0 83 | 84 | def __init__(self): 85 | # EAGLE_HOME_PATH 86 | if not EAGLE_HOME_PATH: 87 | logger.warning(" 没有正确配置. 请填写") 88 | exit() 89 | else: 90 | self.eagle_home_path = EAGLE_HOME_PATH 91 | 92 | # PIXIC_API 93 | # FLAG - False - TEMP URL 94 | # FLAG - True - PIXIC API 95 | if not PIXIC_API: 96 | self.pixic_api = "" 97 | self.flag = False 98 | else: 99 | self.pixic_api = PIXIC_API 100 | self.flag = True 101 | logger.info(f" {self.pixic_api}") 102 | 103 | # AJAX URL 104 | self.temp_url = "https://www.pixiv.net/ajax/illust/" 105 | 106 | # HEADERS 107 | self.headers = { 108 | "Host": "www.pixiv.net", 109 | "referer": "https://www.pixiv.net/", 110 | "origin": "https://accounts.pixiv.net", 111 | "accept-language": "zh-CN,zh;q=0.9", 112 | "User-Agent": 'Mozilla/5.0 (Windows NT 10.0; WOW64) ' 113 | 'AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36' 114 | } 115 | 116 | def baseRequest(self, options, method="GET", data=None, params=None, retry_num=5): 117 | """ 118 | 一个小型的健壮的网络请求函数 119 | :params options: 包括url,自定义headers,超时时间,cookie等 120 | :params method: 请求类型 121 | :params data: POST传参 122 | :params params: GET传参 123 | :params retry_num: 重试次数 124 | 125 | demo如下: 126 | demo_headers = headers.copy() 127 | demo_headers['referer'] = 'www.example.com' 128 | options ={ 129 | "url": origin_url, 130 | "headers": demo_headers 131 | } 132 | baseRequest(options = options) 133 | """ 134 | try: 135 | response = requests.request( 136 | method, 137 | options["url"], 138 | data = data, 139 | params = params, 140 | cookies = options.get("cookies",""), 141 | headers = self.headers, 142 | verify = False, 143 | timeout = options.get("timeout",5), 144 | ) 145 | response.encoding = "utf8" 146 | return response 147 | except Exception as e: 148 | if retry_num > 0: 149 | time.sleep(0.5) 150 | return self.baseRequest(options,data,params,retry_num=retry_num-1) 151 | else: 152 | logger.info("网络请求出错 url:{}".format(options["url"])) 153 | return 154 | 155 | def get_images(self, path=None): 156 | if not path:path = self.eagle_home_path 157 | return os.listdir(path) 158 | 159 | def get_pid(self, name): 160 | """ 161 | 识别和切分pid 162 | :params name: 文件名称 163 | :return: pid or "" 164 | """ 165 | pid = "" 166 | if "-" in name: 167 | pid = name.split("-")[0] 168 | elif "_" in name: 169 | pid = name.split("_")[0] 170 | else: 171 | pid = name.split(".")[0] 172 | 173 | try: 174 | int(pid) 175 | except Exception as e: 176 | return "" 177 | else: 178 | return pid 179 | 180 | # PIXIC API 181 | def get_tags_pixic(self,pid): 182 | """ 183 | 从PIXIC API获取pid tag(get-info API) 184 | 查询不到结果则使用pixiv api进行复查 185 | 186 | :params pid: pixiv插画id 187 | :return: [tag1,tag2...] or [] 188 | """ 189 | data = {"pid": pid} 190 | resp = self.baseRequest( 191 | options = {"url":self.pixic_api}, 192 | method = "POST", 193 | data = data 194 | ) 195 | if not resp: 196 | return [] 197 | 198 | json_data = json.loads(resp.text) 199 | 200 | if json_data["result"][0].get("message","") != "": 201 | # PIXIC API内包含画师名称 202 | tags = json_data["result"][0]["tag"] 203 | tag_list = [] 204 | 205 | _ = tags.split("、") 206 | for i in _: 207 | temp = i.split("/") 208 | for t in temp:tag_list.append(t) 209 | return list(set(tag_list)) 210 | else: 211 | logger.info(json_data["result"][0]["message"]) 212 | return [] 213 | 214 | # AJAX URL 215 | def get_tags(self, pid): 216 | """ 217 | 从pixiv api获取pid tag 218 | :params pid: pixiv插画id 219 | :return: [tag1,tag2...] or [] 220 | """ 221 | resp = self.baseRequest( 222 | options = {"url":f"{self.temp_url}{pid}"} 223 | ) 224 | if not resp: 225 | return [] 226 | 227 | json_data = json.loads(resp.text) 228 | 229 | if json_data["error"] == False: 230 | tags = json_data["body"]["tags"]["tags"] 231 | tag_list = [] 232 | # 加入画师名称 233 | tag_list.append(json_data["body"]["userName"]) 234 | for i in tags: 235 | if "translation" in i.keys(): 236 | tag_list.append(i["translation"]["en"]) 237 | tag_list.append(i["tag"]) 238 | return list(set(tag_list)) 239 | else: 240 | logger.info(json_data["message"]) 241 | return [] 242 | 243 | @logger.catch 244 | def thread_task(self, _, num): 245 | """ 246 | 线程任务函数 247 | :params _: 待处理文件路径 248 | :params num: 当前序号 249 | """ 250 | metajson_path = os.path.join(self.images_path,_,"metadata.json") 251 | # logger.info(f"<{num}/{self.len_images_folders}> {metajson_path}") 252 | with open(metajson_path,encoding="utf8") as f: 253 | # 记载metadata 254 | metadata = json.load(f) 255 | # 获取metafilename 256 | name = metadata["name"] 257 | logger.info(f"<{num}/{self.len_images_folders}> {name} {metajson_path}") 258 | pid = self.get_pid(name) 259 | # 成功识别 260 | if pid: 261 | self._count += 1 262 | # 无法识别 263 | else: 264 | self.count += 1 265 | self.un_count += 1 266 | return 267 | 268 | # TAG 269 | # tags已有内容跳过 270 | if metadata["tags"]: 271 | self.count += 1 272 | self.pass_count += 1 273 | return 274 | else: 275 | tag_list = [] 276 | # 有定义PIXIC API则使用 277 | if self.flag: 278 | tag_list = self.get_tags_pixic(pid) 279 | 280 | # PIXIC获取失败或未定义PIXIC API 281 | if not tag_list: 282 | tag_list = self.get_tags(pid) 283 | 284 | # tag_list获取为空则跳过 285 | if tag_list == []: 286 | self.count += 1 287 | self.pass_count += 1 288 | return 289 | else: 290 | metadata["tags"] = tag_list 291 | 292 | # 写入 293 | if metadata: 294 | with open(metajson_path,"w",encoding="utf8") as f: 295 | json.dump(metadata,f,ensure_ascii=False) 296 | self.succees_count += 1 297 | time.sleep(0.1) 298 | self.count += 1 299 | 300 | def main(self): 301 | self.images_path = os.path.join(self.eagle_home_path,"images") 302 | images_folders = self.get_images(self.images_path) 303 | self.len_images_folders = len(images_folders) 304 | 305 | try: 306 | for _ in range(0,len(images_folders)): 307 | TOOL.put(self.thread_task, (images_folders[_], _+1, ), callback) 308 | # break 309 | except Exception as e: 310 | logger.info("Exception:{}".format(e)) 311 | TOOL.close() 312 | finally: 313 | TOOL.close() 314 | 315 | while True: 316 | logger.info(f" {TOOL.free_list} {TOOL.max_num} {TOOL.generate_list}") 317 | # 正常关闭线程池 318 | if TOOL.free_list == [] and TOOL.generate_list == []: 319 | TOOL.close() 320 | logger.info(f"<当前总数> {self.count}") 321 | logger.info(f"<成功识别数> {self._count}") 322 | logger.info(f"<无法识别数> {self.un_count}") 323 | logger.info(f"<写入成功数> {self.succees_count}") 324 | logger.info(f"<跳过数> {self.pass_count}") 325 | break 326 | time.sleep(3) 327 | 328 | if __name__ == "__main__": 329 | pixiv2eagle = Pixiv2Eagle() 330 | pixiv2eagle.main() -------------------------------------------------------------------------------- /0x09-Pixiv插画tag数据导入Eagle/requirement.txt: -------------------------------------------------------------------------------- 1 | requests 2 | loguru -------------------------------------------------------------------------------- /0x09-Pixiv插画tag数据导入Eagle/thread_pool.py: -------------------------------------------------------------------------------- 1 | # coding=utf8 2 | 3 | """ 4 | 一个基于thread和queue的线程池, 5 | 任务为队列元素,动态创建线程并重复利用. 6 | 通过close和terminate关闭线程池. 7 | """ 8 | 9 | import queue 10 | import threading 11 | import contextlib 12 | import time 13 | 14 | # 创建空对象,用于停止线程 15 | StopEvent = object() 16 | 17 | 18 | def callback(status, result): 19 | """ 20 | 根据需要进行的回调函数,默认不执行。 21 | :param status: action函数的执行状态 22 | :param result: action函数的返回值 23 | :return: 24 | """ 25 | pass 26 | 27 | 28 | def action(thread_name, arg): 29 | """ 30 | 真实的任务定义在这个函数里 31 | :param thread_name: 执行该方法的线程名 32 | :param arg: 该函数需要的参数 33 | :return: 34 | """ 35 | # 模拟执行 36 | time.sleep(0.1) 37 | print("第%s个任务调用了线程 %s,并打印了这条信息!" % (arg+1, thread_name)) 38 | 39 | 40 | class ThreadPool: 41 | 42 | def __init__(self, max_num, max_task_num=None): 43 | """ 44 | 初始化线程池 45 | :param max_num: 线程池最大线程数量 46 | :param max_task_num: 任务队列长度 47 | """ 48 | # 如果提供了最大任务数的参数,则将队列的最大元素个数设置为这个值。 49 | max_task_num = 20 50 | if max_task_num: 51 | self.q = queue.Queue(max_task_num) 52 | # 默认队列可接受无限多个的任务 53 | else: 54 | self.q = queue.Queue() 55 | # print("创建好队列了") 56 | # 设置线程池最多可实例化的线程数 57 | self.max_num = max_num 58 | # 任务取消标识 59 | self.cancel = False 60 | # 任务中断标识 61 | self.terminal = False 62 | # 已实例化的线程列表 63 | self.generate_list = [] 64 | # 处于空闲状态的线程列表 65 | self.free_list = [] 66 | 67 | def put(self, func, args, callback=None): 68 | """ 69 | 往任务队列里放入一个任务 70 | :param func: 任务函数 71 | :param args: 任务函数所需参数 72 | :param callback: 任务执行失败或成功后执行的回调函数,回调函数有两个参数 73 | 1、任务函数执行状态;2、任务函数返回值(默认为None,即:不执行回调函数) 74 | :return: 如果线程池已经终止,则返回True否则None 75 | """ 76 | # 先判断标识,看看任务是否取消了 77 | if self.cancel: 78 | return 79 | # 如果没有空闲的线程,并且已创建的线程的数量小于预定义的最大线程数,则创建新线程。 80 | if len(self.free_list) == 0 and len(self.generate_list) < self.max_num: 81 | self.generate_thread() 82 | # 构造任务参数元组,分别是调用的函数,该函数的参数,回调函数。 83 | w = (func, args, callback,) 84 | # 将任务放入队列 85 | self.q.put(w) 86 | 87 | def generate_thread(self): 88 | """ 89 | 创建一个线程 90 | """ 91 | # 每个线程都执行call方法 92 | t = threading.Thread(target=self.call) 93 | t.start() 94 | 95 | def call(self): 96 | """ 97 | 循环去获取任务函数并执行任务函数。在正常情况下,每个线程都保存生存状态,直到获取线程终止的flag。 98 | """ 99 | # 获取当前线程的名字 100 | current_thread = threading.currentThread().getName() 101 | # 将当前线程的名字加入已实例化的线程列表中 102 | self.generate_list.append(current_thread) 103 | # 从任务队列中获取一个任务 104 | event = self.q.get() 105 | # 让获取的任务不是终止线程的标识对象时 106 | while event != StopEvent: 107 | # 解析任务中封装的三个参数 108 | func, arguments, callback = event 109 | # 抓取异常,防止线程因为异常退出 110 | try: 111 | # 正常执行任务函数 112 | result = func(*arguments) 113 | # result = func(current_thread, *arguments) 114 | success = True 115 | except Exception as e: 116 | # 当任务执行过程中弹出异常 117 | result = None 118 | success = False 119 | # 如果有指定的回调函数 120 | if callback is not None: 121 | # 执行回调函数,并抓取异常 122 | try: 123 | callback(success, result) 124 | except Exception as e: 125 | pass 126 | # 当某个线程正常执行完一个任务时,先执行worker_state方法 127 | with self.worker_state(self.free_list, current_thread): 128 | # 如果强制关闭线程的flag开启,则传入一个StopEvent元素 129 | if self.terminal: 130 | event = StopEvent 131 | # 否则获取一个正常的任务,并回调worker_state方法的yield语句 132 | else: 133 | # 从这里开始又是一个正常的任务循环 134 | event = self.q.get() 135 | else: 136 | # 一旦发现任务是个终止线程的标识元素,将线程从已创建线程列表中删除 137 | self.generate_list.remove(current_thread) 138 | 139 | def close(self): 140 | """ 141 | 执行完所有的任务后,让所有线程都停止的方法 142 | """ 143 | # 设置flag 144 | self.cancel = True 145 | # 计算已创建线程列表中线程的个数, 146 | # 然后往任务队列里推送相同数量的终止线程的标识元素 147 | full_size = len(self.generate_list) 148 | while full_size: 149 | self.q.put(StopEvent) 150 | full_size -= 1 151 | 152 | def terminate(self): 153 | """ 154 | 在任务执行过程中,终止线程,提前退出。 155 | """ 156 | self.terminal = True 157 | # 强制性的停止线程 158 | while self.generate_list: 159 | self.q.put(StopEvent) 160 | 161 | # 该装饰器用于上下文管理 162 | @contextlib.contextmanager 163 | def worker_state(self, state_list, worker_thread): 164 | """ 165 | 用于记录空闲的线程,或从空闲列表中取出线程处理任务 166 | """ 167 | # 将当前线程,添加到空闲线程列表中 168 | state_list.append(worker_thread) 169 | # 捕获异常 170 | try: 171 | # 在此等待 172 | yield 173 | finally: 174 | # 将线程从空闲列表中移除 175 | state_list.remove(worker_thread) 176 | 177 | """ 178 | 179 | # 调用方式 180 | if __name__ == '__main__': 181 | # 创建一个最多包含5个线程的线程池 182 | # pool = ThreadPool(5) 183 | 184 | # 创建100个任务,让线程池进行处理 185 | # for i in range(100): 186 | # pool.put(action, (i,), callback) 187 | 188 | # 等待一定时间,让线程执行任务 189 | # time.sleep(3) 190 | 191 | print("-" * 50) 192 | while True: 193 | print("\033[32;0m任务停止之前线程池中有%s个线程,空闲的线程有%s个!\033[0m" 194 | % (len(pool.generate_list), len(pool.free_list))) 195 | 196 | if len(pool.free_list) == pool.max_num and len(pool.generate_list) == 5: 197 | # 正常关闭线程池 198 | pool.close() 199 | print("任务执行完毕,正常退出!") 200 | break 201 | 202 | time.sleep(1) 203 | # 强制关闭线程池 204 | # pool.terminate() 205 | # print("强制停止任务!") 206 | 207 | """ -------------------------------------------------------------------------------- /0x0a-碧蓝航线-静态立绘解密加密工具/Readme.md: -------------------------------------------------------------------------------- 1 | ## 碧蓝航线静态立绘解密&加密工具 v1.1.1 2 | 3 | --- 4 | 5 | + 这是一个用于**解密**和**重新加密**碧蓝航线立绘的脚本——2022.1.4 6 | + 解密算法是贴吧某篇帖子提供的(已经不记得是哪位用户了,有知道的可以提个issue) 7 | + 加密算法是自己根据解密算法逆推并一步步调试出来的 8 | + 主要用途 9 | 10 | 1、**解密**加密图像从而得到游戏内的立绘文件 11 | 12 | ![](./img/0.png) 13 | 14 | 2、将经过修改的图片**重新加密** 15 | 16 | ![](./img/1.png) 17 | 18 | 19 | 20 | ### 功能 21 | 22 | - [x] 解密碧蓝航线加密立绘 23 | - [x] 将修改过的立绘**重新加密**回去 24 | - [x] 提供0补偿与0.5补偿两个模式,具体可查看[url](./解封包_0补偿与0.5补偿对比测试结果) 25 | - [x] 提供较详细的说明(程序启动时) 26 | - [x] 提供debug模式和日志记录 27 | - [x] 提供批量解密/加密 28 | 29 | 30 | 31 | ### 目录结构 32 | 33 | ```bash 34 | . 35 | |-- Mesh # mesh纹理文件 36 | |-- Picture # 解密立绘文件夹 37 | |-- Readme.md 38 | |-- Test # 提供了部分测试文件 39 | |-- Texture2D # 加密立绘文件夹 40 | |-- Used # 由程序copy保留的加密立绘文件夹 41 | |-- blhx_img_tool.py 42 | |-- img 43 | `-- requirement.txt 44 | ``` 45 | 46 | 47 | 48 | ### 解密立绘 49 | 50 | 需要: 51 | 52 | + 加密立绘 --> `Texture2D`目录 53 | + 加密立绘同名`mesh`文件 --> `Mesh`目录 54 | 55 | 得到: 56 | 57 | + 解密立绘 --> `Picture`目录 58 | + copy加密立绘 --> `Used`目录(用于在加密时使用,不需要动) 59 | 60 | ![](./img/2.png) 61 | 62 | > 非`debug`模式下的`立绘解密还原log` 63 | 64 | --- 65 | 66 | ### 加密立绘 67 | 68 | 需要: 69 | 70 | + 解密立绘 --> `Picture`目录 71 | + 解密立绘同名`mesh`文件 --> `Mesh`目录 72 | + 之前的加密立绘 --> `Used`目录(通常由程序copy) 73 | 74 | 得到: 75 | 76 | + 加密立绘 --> `Texture2D`目录 77 | 78 | ![](./img/3.png) 79 | 80 | > 非`debug`模式下的`立绘加密还原log` 81 | 82 | --- 83 | 84 | ### 使用方法 85 | 86 | ```bash 87 | # 安装依赖 88 | pip install -r requirement.txt 89 | # 运行脚本 90 | python blhx_img_tool.py 91 | ``` 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /0x0a-碧蓝航线-静态立绘解密加密工具/Test/Mesh/beierfasite_4-mesh.obj: -------------------------------------------------------------------------------- 1 | g beierfasite_4-mesh 2 | v -544 79 0 3 | v -544 463 0 4 | v -832 463 0 5 | v -832 79 0 6 | v -320 591 0 7 | v -320 1167 0 8 | v -480 1167 0 9 | v -480 591 0 10 | v -320 15 0 11 | v -320 591 0 12 | v -480 591 0 13 | v -480 15 0 14 | v -128 335 0 15 | v -128 591 0 16 | v -320 591 0 17 | v -320 335 0 18 | v -128 591 0 19 | v -128 847 0 20 | v -320 847 0 21 | v -320 591 0 22 | v -480 655 0 23 | v -480 879 0 24 | v -640 879 0 25 | v -640 655 0 26 | v -480 463 0 27 | v -480 655 0 28 | v -640 655 0 29 | v -640 463 0 30 | v -128 79 0 31 | v -128 175 0 32 | v -320 175 0 33 | v -320 79 0 34 | v -64 335 0 35 | v -64 591 0 36 | v -128 591 0 37 | v -128 335 0 38 | v -64 79 0 39 | v -64 335 0 40 | v -128 335 0 41 | v -128 79 0 42 | v -640 783 0 43 | v -640 879 0 44 | v -768 879 0 45 | v -768 783 0 46 | v -480 175 0 47 | v -480 367 0 48 | v -544 367 0 49 | v -544 175 0 50 | v -192 847 0 51 | v -192 943 0 52 | v -320 943 0 53 | v -320 847 0 54 | v -480 15 0 55 | v -480 175 0 56 | v -544 175 0 57 | v -544 15 0 58 | v -480 975 0 59 | v -480 1103 0 60 | v -544 1103 0 61 | v -544 975 0 62 | v -32 783 0 63 | v -32 879 0 64 | v -96 879 0 65 | v -96 783 0 66 | v -128 239 0 67 | v -128 335 0 68 | v -192 335 0 69 | v -192 239 0 70 | v -480 879 0 71 | v -480 975 0 72 | v -544 975 0 73 | v -544 879 0 74 | v -832 207 0 75 | v -832 367 0 76 | v -864 367 0 77 | v -864 207 0 78 | v -32 175 0 79 | v -32 335 0 80 | v -64 335 0 81 | v -64 175 0 82 | v -32 335 0 83 | v -32 495 0 84 | v -64 495 0 85 | v -64 335 0 86 | v -160 943 0 87 | v -160 1007 0 88 | v -224 1007 0 89 | v -224 943 0 90 | v -128 175 0 91 | v -128 239 0 92 | v -192 239 0 93 | v -192 175 0 94 | v -832 79 0 95 | v -832 207 0 96 | v -864 207 0 97 | v -864 79 0 98 | v 0 367 0 99 | v 0 463 0 100 | v -32 463 0 101 | v -32 367 0 102 | v -288 1007 0 103 | v -288 1103 0 104 | v -320 1103 0 105 | v -320 1007 0 106 | v -640 495 0 107 | v -640 559 0 108 | v -672 559 0 109 | v -672 495 0 110 | v -864 239 0 111 | v -864 303 0 112 | v -896 303 0 113 | v -896 239 0 114 | v -480 1103 0 115 | v -480 1167 0 116 | v -512 1167 0 117 | v -512 1103 0 118 | v -192 175 0 119 | v -192 239 0 120 | v -224 239 0 121 | v -224 175 0 122 | v -288 271 0 123 | v -288 335 0 124 | v -320 335 0 125 | v -320 271 0 126 | v -32 527 0 127 | v -32 591 0 128 | v -64 591 0 129 | v -64 527 0 130 | v -352 1167 0 131 | v -352 1199 0 132 | v -416 1199 0 133 | v -416 1167 0 134 | v -416 1167 0 135 | v -416 1199 0 136 | v -480 1199 0 137 | v -480 1167 0 138 | v -288 943 0 139 | v -288 1007 0 140 | v -320 1007 0 141 | v -320 943 0 142 | v 0 303 0 143 | v 0 367 0 144 | v -32 367 0 145 | v -32 303 0 146 | v -96 783 0 147 | v -96 847 0 148 | v -128 847 0 149 | v -128 783 0 150 | v -96 719 0 151 | v -96 783 0 152 | v -128 783 0 153 | v -128 719 0 154 | v -480 367 0 155 | v -480 399 0 156 | v -512 399 0 157 | v -512 367 0 158 | v -832 399 0 159 | v -832 431 0 160 | v -864 431 0 161 | v -864 399 0 162 | v -672 463 0 163 | v -672 495 0 164 | v -704 495 0 165 | v -704 463 0 166 | v -32 111 0 167 | v -32 143 0 168 | v -64 143 0 169 | v -64 111 0 170 | v -224 175 0 171 | v -224 207 0 172 | v -256 207 0 173 | v -256 175 0 174 | v -192 303 0 175 | v -192 335 0 176 | v -224 335 0 177 | v -224 303 0 178 | v -64 751 0 179 | v -64 783 0 180 | v -96 783 0 181 | v -96 751 0 182 | v -128 975 0 183 | v -128 1007 0 184 | v -160 1007 0 185 | v -160 975 0 186 | v -864 207 0 187 | v -864 239 0 188 | v -896 239 0 189 | v -896 207 0 190 | v -640 463 0 191 | v -640 495 0 192 | v -672 495 0 193 | v -672 463 0 194 | v -160 911 0 195 | v -160 943 0 196 | v -192 943 0 197 | v -192 911 0 198 | v -224 943 0 199 | v -224 975 0 200 | v -256 975 0 201 | v -256 943 0 202 | v -544 975 0 203 | v -544 1007 0 204 | v -576 1007 0 205 | v -576 975 0 206 | vt 0.0009765625 0.001351357 207 | vt 0.0009765625 0.5202702 208 | vt 0.2822266 0.5202702 209 | vt 0.2822266 0.001351357 210 | vt 0.2841797 0.001351357 211 | vt 0.2841797 0.7797297 212 | vt 0.4404297 0.7797297 213 | vt 0.4404297 0.001351357 214 | vt 0.4423828 0.001351357 215 | vt 0.4423828 0.7797297 216 | vt 0.5986328 0.7797297 217 | vt 0.5986328 0.001351357 218 | vt 0.6005859 0.001351357 219 | vt 0.6005859 0.3472973 220 | vt 0.7880859 0.3472973 221 | vt 0.7880859 0.001351357 222 | vt 0.7900391 0.001351357 223 | vt 0.7900391 0.3472973 224 | vt 0.9775391 0.3472973 225 | vt 0.9775391 0.001351357 226 | vt 0.6005859 0.35 227 | vt 0.6005859 0.6527027 228 | vt 0.7568359 0.6527027 229 | vt 0.7568359 0.35 230 | vt 0.7587891 0.35 231 | vt 0.7587891 0.6094595 232 | vt 0.9150391 0.6094595 233 | vt 0.9150391 0.35 234 | vt 0.0009765625 0.5229729 235 | vt 0.0009765625 0.6527027 236 | vt 0.1884766 0.6527027 237 | vt 0.1884766 0.5229729 238 | vt 0.9169922 0.35 239 | vt 0.9169922 0.695946 240 | vt 0.9794922 0.695946 241 | vt 0.9794922 0.35 242 | vt 0.1904297 0.5229729 243 | vt 0.1904297 0.8689189 244 | vt 0.2529297 0.8689189 245 | vt 0.2529297 0.5229729 246 | vt 0.7587891 0.6121622 247 | vt 0.7587891 0.7418919 248 | vt 0.8837891 0.7418919 249 | vt 0.8837891 0.6121622 250 | vt 0.0009765625 0.6554054 251 | vt 0.0009765625 0.9148649 252 | vt 0.06347656 0.9148649 253 | vt 0.06347656 0.6554054 254 | vt 0.6005859 0.6554054 255 | vt 0.6005859 0.7851351 256 | vt 0.7255859 0.7851351 257 | vt 0.7255859 0.6554054 258 | vt 0.06542969 0.6554054 259 | vt 0.06542969 0.8716216 260 | vt 0.1279297 0.8716216 261 | vt 0.1279297 0.6554054 262 | vt 0.9169922 0.6986487 263 | vt 0.9169922 0.8716216 264 | vt 0.9794922 0.8716216 265 | vt 0.9794922 0.6986487 266 | vt 0.7587891 0.7445946 267 | vt 0.7587891 0.8743243 268 | vt 0.8212891 0.8743243 269 | vt 0.8212891 0.7445946 270 | vt 0.8232422 0.7445946 271 | vt 0.8232422 0.8743243 272 | vt 0.8857422 0.8743243 273 | vt 0.8857422 0.7445946 274 | vt 0.2841797 0.7824324 275 | vt 0.2841797 0.9121622 276 | vt 0.3466797 0.9121622 277 | vt 0.3466797 0.7824324 278 | vt 0.1298828 0.6554054 279 | vt 0.1298828 0.8716216 280 | vt 0.1611328 0.8716216 281 | vt 0.1611328 0.6554054 282 | vt 0.3486328 0.7824324 283 | vt 0.3486328 0.9986486 284 | vt 0.3798828 0.9986486 285 | vt 0.3798828 0.7824324 286 | vt 0.3818359 0.7824324 287 | vt 0.3818359 0.9986486 288 | vt 0.4130859 0.9986486 289 | vt 0.4130859 0.7824324 290 | vt 0.4150391 0.7824324 291 | vt 0.4150391 0.8689189 292 | vt 0.4775391 0.8689189 293 | vt 0.4775391 0.7824324 294 | vt 0.4794922 0.7824324 295 | vt 0.4794922 0.8689189 296 | vt 0.5419922 0.8689189 297 | vt 0.5419922 0.7824324 298 | vt 0.5439453 0.7824324 299 | vt 0.5439453 0.9554054 300 | vt 0.5751953 0.9554054 301 | vt 0.5751953 0.7824324 302 | vt 0.6005859 0.7878379 303 | vt 0.6005859 0.9175676 304 | vt 0.6318359 0.9175676 305 | vt 0.6318359 0.7878379 306 | vt 0.6337891 0.7878379 307 | vt 0.6337891 0.9175676 308 | vt 0.6650391 0.9175676 309 | vt 0.6650391 0.7878379 310 | vt 0.6669922 0.7878379 311 | vt 0.6669922 0.8743243 312 | vt 0.6982422 0.8743243 313 | vt 0.6982422 0.7878379 314 | vt 0.7001953 0.7878379 315 | vt 0.7001953 0.8743243 316 | vt 0.7314453 0.8743243 317 | vt 0.7314453 0.7878379 318 | vt 0.1904297 0.8716216 319 | vt 0.1904297 0.9581081 320 | vt 0.2216797 0.9581081 321 | vt 0.2216797 0.8716216 322 | vt 0.2236328 0.8716216 323 | vt 0.2236328 0.9581081 324 | vt 0.2548828 0.9581081 325 | vt 0.2548828 0.8716216 326 | vt 0.4150391 0.8716216 327 | vt 0.4150391 0.9581081 328 | vt 0.4462891 0.9581081 329 | vt 0.4462891 0.8716216 330 | vt 0.4482422 0.8716216 331 | vt 0.4482422 0.9581081 332 | vt 0.4794922 0.9581081 333 | vt 0.4794922 0.8716216 334 | vt 0.06542969 0.8743243 335 | vt 0.06542969 0.9175676 336 | vt 0.1279297 0.9175676 337 | vt 0.1279297 0.8743243 338 | vt 0.9169922 0.8743243 339 | vt 0.9169922 0.9175676 340 | vt 0.9794922 0.9175676 341 | vt 0.9794922 0.8743243 342 | vt 0.4814453 0.8716216 343 | vt 0.4814453 0.9581081 344 | vt 0.5126953 0.9581081 345 | vt 0.5126953 0.8716216 346 | vt 0.1298828 0.8743243 347 | vt 0.1298828 0.9608108 348 | vt 0.1611328 0.9608108 349 | vt 0.1611328 0.8743243 350 | vt 0.6669922 0.877027 351 | vt 0.6669922 0.9635135 352 | vt 0.6982422 0.9635135 353 | vt 0.6982422 0.877027 354 | vt 0.7001953 0.877027 355 | vt 0.7001953 0.9635135 356 | vt 0.7314453 0.9635135 357 | vt 0.7314453 0.877027 358 | vt 0.7587891 0.877027 359 | vt 0.7587891 0.9202703 360 | vt 0.7900391 0.9202703 361 | vt 0.7900391 0.877027 362 | vt 0.7919922 0.877027 363 | vt 0.7919922 0.9202703 364 | vt 0.8232422 0.9202703 365 | vt 0.8232422 0.877027 366 | vt 0.8251953 0.877027 367 | vt 0.8251953 0.9202703 368 | vt 0.8564453 0.9202703 369 | vt 0.8564453 0.877027 370 | vt 0.8583984 0.877027 371 | vt 0.8583984 0.9202703 372 | vt 0.8896484 0.9202703 373 | vt 0.8896484 0.877027 374 | vt 0.2841797 0.9148649 375 | vt 0.2841797 0.9581081 376 | vt 0.3154297 0.9581081 377 | vt 0.3154297 0.9148649 378 | vt 0.0009765625 0.9175676 379 | vt 0.0009765625 0.9608108 380 | vt 0.03222656 0.9608108 381 | vt 0.03222656 0.9175676 382 | vt 0.06542969 0.9202703 383 | vt 0.06542969 0.9635135 384 | vt 0.09667969 0.9635135 385 | vt 0.09667969 0.9202703 386 | vt 0.6005859 0.9202703 387 | vt 0.6005859 0.9635135 388 | vt 0.6318359 0.9635135 389 | vt 0.6318359 0.9202703 390 | vt 0.6337891 0.9202703 391 | vt 0.6337891 0.9635135 392 | vt 0.6650391 0.9635135 393 | vt 0.6650391 0.9202703 394 | vt 0.9169922 0.9202703 395 | vt 0.9169922 0.9635135 396 | vt 0.9482422 0.9635135 397 | vt 0.9482422 0.9202703 398 | vt 0.9501953 0.9202703 399 | vt 0.9501953 0.9635135 400 | vt 0.9814453 0.9635135 401 | vt 0.9814453 0.9202703 402 | vt 0.7587891 0.922973 403 | vt 0.7587891 0.9662162 404 | vt 0.7900391 0.9662162 405 | vt 0.7900391 0.922973 406 | vt 0.7919922 0.922973 407 | vt 0.7919922 0.9662162 408 | vt 0.8232422 0.9662162 409 | vt 0.8232422 0.922973 410 | g beierfasite_4-mesh_0 411 | f 3/3/3 2/2/2 1/1/1 412 | f 1/1/1 4/4/4 3/3/3 413 | f 7/7/7 6/6/6 5/5/5 414 | f 5/5/5 8/8/8 7/7/7 415 | f 11/11/11 10/10/10 9/9/9 416 | f 9/9/9 12/12/12 11/11/11 417 | f 15/15/15 14/14/14 13/13/13 418 | f 13/13/13 16/16/16 15/15/15 419 | f 19/19/19 18/18/18 17/17/17 420 | f 17/17/17 20/20/20 19/19/19 421 | f 23/23/23 22/22/22 21/21/21 422 | f 21/21/21 24/24/24 23/23/23 423 | f 27/27/27 26/26/26 25/25/25 424 | f 25/25/25 28/28/28 27/27/27 425 | f 31/31/31 30/30/30 29/29/29 426 | f 29/29/29 32/32/32 31/31/31 427 | f 35/35/35 34/34/34 33/33/33 428 | f 33/33/33 36/36/36 35/35/35 429 | f 39/39/39 38/38/38 37/37/37 430 | f 37/37/37 40/40/40 39/39/39 431 | f 43/43/43 42/42/42 41/41/41 432 | f 41/41/41 44/44/44 43/43/43 433 | f 47/47/47 46/46/46 45/45/45 434 | f 45/45/45 48/48/48 47/47/47 435 | f 51/51/51 50/50/50 49/49/49 436 | f 49/49/49 52/52/52 51/51/51 437 | f 55/55/55 54/54/54 53/53/53 438 | f 53/53/53 56/56/56 55/55/55 439 | f 59/59/59 58/58/58 57/57/57 440 | f 57/57/57 60/60/60 59/59/59 441 | f 63/63/63 62/62/62 61/61/61 442 | f 61/61/61 64/64/64 63/63/63 443 | f 67/67/67 66/66/66 65/65/65 444 | f 65/65/65 68/68/68 67/67/67 445 | f 71/71/71 70/70/70 69/69/69 446 | f 69/69/69 72/72/72 71/71/71 447 | f 75/75/75 74/74/74 73/73/73 448 | f 73/73/73 76/76/76 75/75/75 449 | f 79/79/79 78/78/78 77/77/77 450 | f 77/77/77 80/80/80 79/79/79 451 | f 83/83/83 82/82/82 81/81/81 452 | f 81/81/81 84/84/84 83/83/83 453 | f 87/87/87 86/86/86 85/85/85 454 | f 85/85/85 88/88/88 87/87/87 455 | f 91/91/91 90/90/90 89/89/89 456 | f 89/89/89 92/92/92 91/91/91 457 | f 95/95/95 94/94/94 93/93/93 458 | f 93/93/93 96/96/96 95/95/95 459 | f 99/99/99 98/98/98 97/97/97 460 | f 97/97/97 100/100/100 99/99/99 461 | f 103/103/103 102/102/102 101/101/101 462 | f 101/101/101 104/104/104 103/103/103 463 | f 107/107/107 106/106/106 105/105/105 464 | f 105/105/105 108/108/108 107/107/107 465 | f 111/111/111 110/110/110 109/109/109 466 | f 109/109/109 112/112/112 111/111/111 467 | f 115/115/115 114/114/114 113/113/113 468 | f 113/113/113 116/116/116 115/115/115 469 | f 119/119/119 118/118/118 117/117/117 470 | f 117/117/117 120/120/120 119/119/119 471 | f 123/123/123 122/122/122 121/121/121 472 | f 121/121/121 124/124/124 123/123/123 473 | f 127/127/127 126/126/126 125/125/125 474 | f 125/125/125 128/128/128 127/127/127 475 | f 131/131/131 130/130/130 129/129/129 476 | f 129/129/129 132/132/132 131/131/131 477 | f 135/135/135 134/134/134 133/133/133 478 | f 133/133/133 136/136/136 135/135/135 479 | f 139/139/139 138/138/138 137/137/137 480 | f 137/137/137 140/140/140 139/139/139 481 | f 143/143/143 142/142/142 141/141/141 482 | f 141/141/141 144/144/144 143/143/143 483 | f 147/147/147 146/146/146 145/145/145 484 | f 145/145/145 148/148/148 147/147/147 485 | f 151/151/151 150/150/150 149/149/149 486 | f 149/149/149 152/152/152 151/151/151 487 | f 155/155/155 154/154/154 153/153/153 488 | f 153/153/153 156/156/156 155/155/155 489 | f 159/159/159 158/158/158 157/157/157 490 | f 157/157/157 160/160/160 159/159/159 491 | f 163/163/163 162/162/162 161/161/161 492 | f 161/161/161 164/164/164 163/163/163 493 | f 167/167/167 166/166/166 165/165/165 494 | f 165/165/165 168/168/168 167/167/167 495 | f 171/171/171 170/170/170 169/169/169 496 | f 169/169/169 172/172/172 171/171/171 497 | f 175/175/175 174/174/174 173/173/173 498 | f 173/173/173 176/176/176 175/175/175 499 | f 179/179/179 178/178/178 177/177/177 500 | f 177/177/177 180/180/180 179/179/179 501 | f 183/183/183 182/182/182 181/181/181 502 | f 181/181/181 184/184/184 183/183/183 503 | f 187/187/187 186/186/186 185/185/185 504 | f 185/185/185 188/188/188 187/187/187 505 | f 191/191/191 190/190/190 189/189/189 506 | f 189/189/189 192/192/192 191/191/191 507 | f 195/195/195 194/194/194 193/193/193 508 | f 193/193/193 196/196/196 195/195/195 509 | f 199/199/199 198/198/198 197/197/197 510 | f 197/197/197 200/200/200 199/199/199 511 | f 203/203/203 202/202/202 201/201/201 512 | f 201/201/201 204/204/204 203/203/203 513 | -------------------------------------------------------------------------------- /0x0a-碧蓝航线-静态立绘解密加密工具/Test/Picture/beierfasite_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WriteCode-ChangeWorld/Tools/096dd8af8b9d1f501187ce62ab38e6be07f1518d/0x0a-碧蓝航线-静态立绘解密加密工具/Test/Picture/beierfasite_4.png -------------------------------------------------------------------------------- /0x0a-碧蓝航线-静态立绘解密加密工具/Test/Texture2D/beierfasite_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WriteCode-ChangeWorld/Tools/096dd8af8b9d1f501187ce62ab38e6be07f1518d/0x0a-碧蓝航线-静态立绘解密加密工具/Test/Texture2D/beierfasite_4.png -------------------------------------------------------------------------------- /0x0a-碧蓝航线-静态立绘解密加密工具/blhx_img_tool.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | ''' 3 | @File : blhx_img_tool.py 4 | @Time : 2021/12/24 17:07:46 5 | @Author : Coder-Sakura 6 | @Version : 1.1 7 | @Desc : 碧蓝航线静态立绘加解密工具 8 | ''' 9 | 10 | # here put the import lib 11 | import os 12 | import cv2 13 | import sys 14 | import time 15 | import shutil 16 | import numpy as np 17 | from loguru import logger 18 | 19 | 20 | # DEBUG 21 | DEBUG = False 22 | 23 | # log config 24 | level = "DEBUG" if DEBUG else "INFO" 25 | logger.remove() 26 | logger.add( 27 | sys.stderr, 28 | level=level 29 | ) 30 | log_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "log") 31 | # 日志写入 32 | logger.add( 33 | os.path.join(log_path, "{time}.log"), 34 | encoding="utf-8", 35 | rotation="00:00", 36 | enqueue=True, 37 | level="INFO" 38 | ) 39 | 40 | 41 | class tool: 42 | """公共方法""" 43 | check_list = [ 44 | "Mesh", # obj文件 45 | "Picture", # 解密立绘 46 | "Texture2D", # 加密立绘 47 | "Used" # 处理过的加密立绘 48 | ] 49 | 50 | def __init__(self): 51 | self.check_folders() 52 | 53 | def check_folders(self): 54 | """检查文件夹""" 55 | for i in tool.check_list: 56 | _ = os.path.join(os.getcwd(),i) 57 | if not os.path.exists(_): 58 | os.mkdir(_) 59 | logger.debug(f"Path Not Found: {_}. 请在脚本当前目录创建<{i}>文件夹") 60 | 61 | def get_material(self): 62 | """ 63 | 获取文件夹文件 64 | :return: 65 | [ 66 | {"Mesh": ["22-mesh.obj"]}, 67 | {"Texture2D": ["22.png"]} 68 | ] 69 | """ 70 | result = {} 71 | for i in tool.check_list: 72 | result[i] = os.listdir(os.path.join(os.getcwd(),i)) 73 | return result 74 | 75 | def get_shape(self,img): 76 | """ 77 | return Img height and weight 78 | """ 79 | return img.shape[0], img.shape[1] 80 | 81 | def read_file(self,mesh_path): 82 | """ 83 | 读取mesh文件,去除\n后以空格为间隔切分返回 84 | :params s: Mesh文件地址 85 | :return: [["v",1,1,0], ...] 86 | """ 87 | List1 = [] 88 | Filer = open(mesh_path,'r') 89 | List2 = Filer.readlines() 90 | Filer.close() 91 | for i0 in range(0, len(List2)): 92 | List2[i0] = List2[i0].replace("\n", "") 93 | List1.append(List2[i0].split()) 94 | return List1 95 | 96 | def read_png(self,png_path): 97 | """ 98 | 读取PNG图片 - 参考: https://blog.csdn.net/weixin_44015965/article/details/109547129 99 | cv2.IMREAD_COLOR:默认参数,读入一副彩色图片,忽略alpha通道,可用1作为实参替代 100 | cv2.IMREAD_GRAYSCALE:读入灰度图片,可用0作为实参替代 101 | cv2.IMREAD_UNCHANGED:顾名思义,读入完整图片,包括alpha通道,可用-1作为实参替代 102 | PS: alpha通道,又称A通道,是一个8位的灰度通道,该通道用256级灰度来记录图像中的透明度复信息,\ 103 | 定义透明、不透明和半透明区域,其中黑表示全透明,白表示不透明,灰表示半透明 104 | :params png_path: PNG文件路径 105 | :returnL img obj 106 | """ 107 | img = cv2.imread(png_path, cv2.IMREAD_UNCHANGED) 108 | return img 109 | 110 | def save_png(self, file_path, img): 111 | """ 112 | 以PNG形式保存图像 113 | :params file_path: 保存路径 114 | :params img: img obj 115 | :return: None 116 | """ 117 | cv2.imwrite(file_path, img, [int(cv2.IMWRITE_PNG_COMPRESSION), 9]) 118 | 119 | def rotate(self, img, flipCode=-1): 120 | """ 121 | 根据翻转代码来翻转图片 122 | cv2.flip(image, 1) # 水平翻转 123 | cv2.flip(image, 0) # 垂直翻转 124 | cv2.flip(image, -1) # 水平垂直翻转 180°垂直翻转一次+180°水平翻转一次 125 | 126 | :params img: img obj 127 | :params flipCode: 翻转代码 128 | :return: new_img obj 129 | """ 130 | new_img = cv2.flip(img, flipCode) 131 | return new_img 132 | 133 | 134 | class encrypt_img: 135 | """封包 - 将修改好的立绘加密为碎片图""" 136 | def __init__(self): 137 | self.compensation = True 138 | 139 | def task(self, mesh_obj, pic): 140 | """ 141 | :params mesh_obj: mesh文件名称 142 | :params pic: 加密或解密图片名称 143 | """ 144 | logger.info(f" ===== 正在处理: <{pic}> ===== ") 145 | 146 | # mesh数据 147 | mesh = os.path.join(".", "Mesh", mesh_obj) 148 | # 解密后的正常立绘 149 | picture = os.path.join(".", "Picture", pic) 150 | # 游戏内拆包出来的加密图 151 | texture = os.path.join(".", "Used", pic) 152 | # 最后的结果图片路径 153 | final_png = os.path.join(".", "Texture2D", pic) 154 | logger.debug(f" - {mesh}") 155 | logger.debug(f" - {picture}") 156 | logger.debug(f" - {texture}") 157 | logger.debug(f" - {final_png}") 158 | 159 | mesh_data = Tool.read_file(mesh) 160 | img = Tool.read_png(picture) 161 | texture_img = Tool.read_png(texture) 162 | 163 | vjShu = firstx = endx = firsty = endy = 0 164 | # 解密图 - 高,宽 165 | # height,weight = img.shape[0],img.shape[1] 166 | # 根据之前的加密图创建还原画布 167 | try: 168 | max_height, max_weight = texture_img.shape[0],texture_img.shape[1] 169 | except Exception as e: 170 | logger.warning("无法获取原加密文件尺寸信息") 171 | logger.warning(f"请尝试将拆包出的<{pic}>加密文件放置于Used文件夹下") 172 | return 173 | 174 | # 水平垂直翻转 175 | img = Tool.rotate(img, -1) 176 | img = Tool.rotate(img,1) 177 | 178 | # 第一个vt坐标的序列 179 | vt_index = 0 180 | for i in range(1, len(mesh_data)): 181 | # 第一个v所在行 1 182 | if mesh_data[i][0] == 'v' and vjShu == 0: 183 | vjShu = i 184 | 185 | if mesh_data[i][0] != "vt" and mesh_data[i-1][0] == "vt": 186 | break 187 | 188 | if mesh_data[i][0] == "vt": 189 | if vt_index == 0: 190 | vt_index = i 191 | 192 | # 创建画布前打印信息 193 | logger.debug(f" - {max_height}") 194 | logger.debug(f" - {max_weight}") 195 | logger.debug(f" - {vjShu}") 196 | logger.debug("vjShu | firstx | firsty | endx | endy | x1 | y1") 197 | 198 | # 根据最大宽高创建画布 199 | Base_img = np.zeros([max_height, max_weight, 4], np.uint8) 200 | jShu = vjShu 201 | while vjShu < len(mesh_data): 202 | if mesh_data[vjShu][0] != 'v': 203 | break 204 | 205 | # 选择解密图中的图像 v第一组 206 | firsty = abs(int(mesh_data[vjShu][1])) 207 | firstx = abs(int(mesh_data[vjShu][2])) 208 | endy = abs(int(mesh_data[vjShu+2][1])) 209 | endx = abs(int(mesh_data[vjShu+2][2])) 210 | # 锁定粘贴的坐标 vt第一组 211 | if self.compensation: 212 | y1 = int(float(mesh_data[vjShu-jShu+vt_index][1]) * float(max_weight) + 0.5) 213 | x1 = int(float(mesh_data[vjShu-jShu+vt_index][2]) * float(max_height) + 0.5) 214 | else: 215 | y1 = int(float(mesh_data[vjShu-jShu+vt_index][1]) * float(max_weight)) 216 | x1 = int(float(mesh_data[vjShu-jShu+vt_index][2]) * float(max_height)) 217 | 218 | logger.debug(f"{vjShu},{firstx},{firsty},{endx},{endy},{x1},{y1}") 219 | 220 | for i in range(firstx-1, endx+1): 221 | for j in range(firsty-1, endy+1): 222 | Base_img[x1+i-firstx][y1+j-firsty] = img[i][j] 223 | 224 | # Tool.save_png(final_png, Base_img) 225 | 226 | vjShu += 4 227 | 228 | # 垂直翻转 229 | Base_img = Tool.rotate(Base_img, 0) 230 | Tool.save_png(final_png, Base_img) 231 | logger.info(f"处理完成: <{pic}> 路径: {final_png}") 232 | 233 | def main(self,compensation=True): 234 | # compensation 默认开启0.5补偿 235 | self.compensation = compensation 236 | 237 | material = Tool.get_material() 238 | Mesh_list = material.get("Mesh", []) 239 | Picture_list = material.get("Picture", []) 240 | Texture2D_list = material.get("Texture2D", []) 241 | Used_list = material.get("Used", []) 242 | 243 | logger.info(f"检测到{len(Picture_list)}组文件...") 244 | for i in range(0, len(Picture_list)): 245 | mesh_obj = Picture_list[i].replace(".png", "-mesh.obj") 246 | if mesh_obj not in Mesh_list: 247 | logger.warning(f"Can't Find {mesh_obj} in Mesh.") 248 | else: 249 | time_start = time.time() 250 | self.task(mesh_obj, Picture_list[i]) 251 | time_end = time.time() 252 | logger.info(f"处理完毕. 耗时 - {round(time_end-time_start,2)}") 253 | logger.success("Tasks Over.") 254 | os.system("pause") 255 | 256 | 257 | class decrypt_img: 258 | """解包 - 将解包出来的加密立绘解密""" 259 | def task(self, mesh_obj, pic): 260 | """ 261 | :params mesh_obj: mesh文件名称 262 | :params pic: 加密或解密图片名称 263 | """ 264 | logger.info(f"\n ===== 正在处理: <{pic}> ===== ") 265 | 266 | # mesh数据 267 | mesh = os.path.join(".", "Mesh", mesh_obj) 268 | # 游戏内拆包出来的加密图 269 | texture = os.path.join(".", "Texture2D", pic) 270 | # 最后的结果图片路径 271 | final_png = os.path.join(".", "Picture", pic) 272 | logger.debug(f" - {mesh}") 273 | logger.debug(f" - {texture}") 274 | logger.debug(f" - {final_png}") 275 | 276 | mesh_data = Tool.read_file(mesh) 277 | img = Tool.read_png(texture) 278 | 279 | max_height = max_weight = vtjShu = firstx = endx = firsty = endy = 0 280 | # 加密图 宽,高 281 | height,weight = img.shape[0],img.shape[1] 282 | 283 | # 垂直翻转 284 | img = Tool.rotate(img,0) 285 | 286 | for i in range(1, len(mesh_data)): 287 | if mesh_data[i][0] != 'v': 288 | # beierfasite为例 - 205 289 | vtjShu = i 290 | break 291 | if abs(int(mesh_data[i][1])) > max_weight: 292 | max_weight = abs(int(mesh_data[i][1])) 293 | if abs(int(mesh_data[i][2])) > max_height: 294 | max_height = abs(int(mesh_data[i][2])) 295 | 296 | # 创建画布前打印信息 297 | logger.debug(f" - {max_height}") 298 | logger.debug(f" - {max_weight}") 299 | logger.debug(f" - {vtjShu}") 300 | logger.debug("vtjShu | firstx | firsty | endx | endy | x1 | y1") 301 | 302 | # 根据得出的最大宽高创建画布 303 | Base_img = np.zeros([max_height+1, max_weight+1, 4], np.uint8) 304 | jShu = vtjShu 305 | while vtjShu < len(mesh_data): 306 | if mesh_data[vtjShu][0] != 'vt': 307 | break 308 | 309 | # 选择加密图中的图像 vt第一组 310 | firsty = int(float(mesh_data[vtjShu][1]) * float(weight) + 0.5) 311 | firstx = int(float(mesh_data[vtjShu][2]) * float(height) + 0.5) 312 | endy = int(float(mesh_data[vtjShu+2][1]) * float(weight) + 0.5) 313 | endx = int(float(mesh_data[vtjShu+2][2]) * float(height) + 0.5) 314 | # 锁定粘贴的坐标 v第一组 315 | y1 = abs(int(mesh_data[vtjShu-jShu+1][1])) 316 | x1 = abs(int(mesh_data[vtjShu-jShu+1][2])) 317 | 318 | logger.debug(f"{vtjShu},{firstx},{firsty},{endx},{endy},{x1},{y1}") 319 | 320 | for i in range(firstx, endx): 321 | for j in range(firsty, endy): 322 | Base_img[x1+i-firstx][y1+j-firsty] = img[i][j] 323 | 324 | # Tool.save_png(final_png, Base_img) 325 | 326 | vtjShu += 4 327 | 328 | # 水平垂直翻转 329 | Base_img = Tool.rotate(Base_img,-1) 330 | Base_img = Tool.rotate(Base_img,1) 331 | Tool.save_png(final_png, Base_img) 332 | logger.info(f"处理完成: <{pic}> 路径: {final_png}") 333 | 334 | def main(self): 335 | material = Tool.get_material() 336 | Mesh_list = material.get("Mesh", []) 337 | Picture_list = material.get("Picture", []) 338 | Texture2D_list = material.get("Texture2D", []) 339 | Used_list = material.get("Used", []) 340 | 341 | logger.info(f"检测到{len(Texture2D_list)}组文件...") 342 | for i in range(0, len(Texture2D_list)): 343 | mesh_obj = Texture2D_list[i].replace(".png", "-mesh.obj") 344 | if mesh_obj not in Mesh_list: 345 | logger.warning(f"Can't Find {mesh_obj} in Mesh.") 346 | else: 347 | time_start = time.time() 348 | self.task(mesh_obj, Texture2D_list[i]) 349 | time_end = time.time() 350 | logger.info(f"处理完毕. 耗时 - {round(time_end-time_start,2)}") 351 | shutil.copy("./Texture2D/" + Texture2D_list[i], "./Used/" + Texture2D_list[i]) 352 | logger.success("Tasks Over.") 353 | os.system("pause") 354 | 355 | 356 | class manager: 357 | """处理流程""" 358 | help_info = """\n === 欢迎使用碧蓝航线立绘加解密工具 ===\n"""\ 359 | """代码托管于: https://github.com/WriteCode-ChangeWorld/Tools 欢迎star~\n\n"""\ 360 | """ ===== 说明 ===== \n1.立绘解密: 将mesh文件 (如22.mesh.obj) 放在文件夹, 将加密文件"""\ 361 | """ (如:22.png) 放在文件夹. \n脚本执行完成后,解密文件在文件夹. """\ 362 | """\n\n2.立绘加密: 将mesh文件 (如22.mesh.obj) 放在文件夹, 将魔改好或要替换立绘的解密文件"""\ 363 | """ (如:22.png) 放在文件夹, 同时将同名加密立绘文件 (如:22.png) 放在文件夹. """\ 364 | """\n脚本执行完成后,加密文件在文件夹. """\ 365 | """\n\n>> 特别说明: 如果是先执行1再执行2, 通常程序会copy一份加密文件到文件夹. 此时不用再手动copy <<"""\ 366 | """\n\n\n === 支持功能 === \n请将文件放置好后再运行本程序\n1.立绘解密还原\n2.立绘加密封包(+0.5补偿, 一般建议)"""\ 367 | """\n3.立绘加密封包(无0.5补偿, 使用<2>封包的图片有细微白线,可尝试使用该选项)\n4.退出"""\ 368 | """\n\n>> 特别说明: 针对<3>选项,请将封包后ok的图片移动到其他文件夹后再使用,以避免带来麻烦. <<""" 369 | 370 | def __init__(self): 371 | self.command_info = { 372 | "1": "Decrypt_Img.main()", # 解密还原 373 | "2": "Encrypt_Img.main()", # 加密封包 374 | "3": "Encrypt_Img.main(compensation=False)", # 加密封包-无0.5补偿 375 | "4": "exit()" # 退出 376 | } 377 | 378 | def main(self): 379 | """主函数""" 380 | print(manager.help_info) 381 | mode = input("回复数字以使用功能: ") 382 | if str(mode) not in list(self.command_info.keys()): 383 | logger.warning(f": {mode} 不在支持列表内,请重新输入或检查输入内容...") 384 | exit() 385 | else: 386 | eval(self.command_info[mode]) 387 | 388 | """ 389 | # Mesh文件 390 | Mesh_files = os.listdir("./Mesh") 391 | # 加密图像文件 392 | Texture2D_files = os.listdir("./Texture2D") 393 | # 解密图像/魔改图像文件 394 | Picture_files = os.listdir("./Picture") 395 | Encrypt_Img.main() 396 | """ 397 | 398 | 399 | Tool = tool() 400 | Encrypt_Img = encrypt_img() 401 | Decrypt_Img = decrypt_img() 402 | Manager = manager() 403 | Manager.main() -------------------------------------------------------------------------------- /0x0a-碧蓝航线-静态立绘解密加密工具/img/0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WriteCode-ChangeWorld/Tools/096dd8af8b9d1f501187ce62ab38e6be07f1518d/0x0a-碧蓝航线-静态立绘解密加密工具/img/0.png -------------------------------------------------------------------------------- /0x0a-碧蓝航线-静态立绘解密加密工具/img/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WriteCode-ChangeWorld/Tools/096dd8af8b9d1f501187ce62ab38e6be07f1518d/0x0a-碧蓝航线-静态立绘解密加密工具/img/1.png -------------------------------------------------------------------------------- /0x0a-碧蓝航线-静态立绘解密加密工具/img/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WriteCode-ChangeWorld/Tools/096dd8af8b9d1f501187ce62ab38e6be07f1518d/0x0a-碧蓝航线-静态立绘解密加密工具/img/2.png -------------------------------------------------------------------------------- /0x0a-碧蓝航线-静态立绘解密加密工具/img/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WriteCode-ChangeWorld/Tools/096dd8af8b9d1f501187ce62ab38e6be07f1518d/0x0a-碧蓝航线-静态立绘解密加密工具/img/3.png -------------------------------------------------------------------------------- /0x0a-碧蓝航线-静态立绘解密加密工具/requirement.txt: -------------------------------------------------------------------------------- 1 | numpy 2 | loguru 3 | opencv-python -------------------------------------------------------------------------------- /0x0a-碧蓝航线-静态立绘解密加密工具/解封包_0补偿与0.5补偿对比测试结果/test_result.txt: -------------------------------------------------------------------------------- 1 | 1、对比测试——解包 2 | 名称 补偿 结果 备注 3 | 镇海 +0.5 √ 4 | 镇海 +0 × 左下角与右下角有白色横线 5 | 贝尔法斯特 +0.5 √ 6 | 贝尔法斯特 +0 ×明显白线区域 7 | 8 | 对比:0.5更优 9 | 10 | 2、对比测试——封包(使用补偿+0.5得出的图片进行测试) 11 | 名称 补偿 结果 备注 12 | 镇海 +0.5 × 明显白线区域 13 | 镇海 +0 √ 与游戏内的拆包出的文件对比,有些微白线 14 | 贝尔法斯特 +0.5 √ 与游戏内拆包文件 一致 15 | 贝尔法斯特 +0 √ 与游戏内的拆包出的文件对比,有些微白线 16 | 17 | 对比: 18 | 部分0.5优(22、贝尔法斯特) 19 | 部分0优(镇海) -------------------------------------------------------------------------------- /0x0a-碧蓝航线-静态立绘解密加密工具/解封包_0补偿与0.5补偿对比测试结果/封包_+0.5_×_明显白线区域_zhenhai_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WriteCode-ChangeWorld/Tools/096dd8af8b9d1f501187ce62ab38e6be07f1518d/0x0a-碧蓝航线-静态立绘解密加密工具/解封包_0补偿与0.5补偿对比测试结果/封包_+0.5_×_明显白线区域_zhenhai_2.png -------------------------------------------------------------------------------- /0x0a-碧蓝航线-静态立绘解密加密工具/解封包_0补偿与0.5补偿对比测试结果/封包_+0.5_√_与游戏内拆包文件 一致_beierfasite_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WriteCode-ChangeWorld/Tools/096dd8af8b9d1f501187ce62ab38e6be07f1518d/0x0a-碧蓝航线-静态立绘解密加密工具/解封包_0补偿与0.5补偿对比测试结果/封包_+0.5_√_与游戏内拆包文件 一致_beierfasite_4.png -------------------------------------------------------------------------------- /0x0a-碧蓝航线-静态立绘解密加密工具/解封包_0补偿与0.5补偿对比测试结果/封包_+0_√_zhenhai_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WriteCode-ChangeWorld/Tools/096dd8af8b9d1f501187ce62ab38e6be07f1518d/0x0a-碧蓝航线-静态立绘解密加密工具/解封包_0补偿与0.5补偿对比测试结果/封包_+0_√_zhenhai_2.png -------------------------------------------------------------------------------- /0x0a-碧蓝航线-静态立绘解密加密工具/解封包_0补偿与0.5补偿对比测试结果/封包_+0_√_与游戏内的拆包出的文件对比,有些微白线_beierfasite_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WriteCode-ChangeWorld/Tools/096dd8af8b9d1f501187ce62ab38e6be07f1518d/0x0a-碧蓝航线-静态立绘解密加密工具/解封包_0补偿与0.5补偿对比测试结果/封包_+0_√_与游戏内的拆包出的文件对比,有些微白线_beierfasite_4.png -------------------------------------------------------------------------------- /0x0a-碧蓝航线-静态立绘解密加密工具/解封包_0补偿与0.5补偿对比测试结果/测试图片/Picture/beierfasite_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WriteCode-ChangeWorld/Tools/096dd8af8b9d1f501187ce62ab38e6be07f1518d/0x0a-碧蓝航线-静态立绘解密加密工具/解封包_0补偿与0.5补偿对比测试结果/测试图片/Picture/beierfasite_4.png -------------------------------------------------------------------------------- /0x0a-碧蓝航线-静态立绘解密加密工具/解封包_0补偿与0.5补偿对比测试结果/测试图片/Picture/zhenhai_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WriteCode-ChangeWorld/Tools/096dd8af8b9d1f501187ce62ab38e6be07f1518d/0x0a-碧蓝航线-静态立绘解密加密工具/解封包_0补偿与0.5补偿对比测试结果/测试图片/Picture/zhenhai_2.png -------------------------------------------------------------------------------- /0x0a-碧蓝航线-静态立绘解密加密工具/解封包_0补偿与0.5补偿对比测试结果/测试图片/Texture2D/beierfasite_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WriteCode-ChangeWorld/Tools/096dd8af8b9d1f501187ce62ab38e6be07f1518d/0x0a-碧蓝航线-静态立绘解密加密工具/解封包_0补偿与0.5补偿对比测试结果/测试图片/Texture2D/beierfasite_4.png -------------------------------------------------------------------------------- /0x0a-碧蓝航线-静态立绘解密加密工具/解封包_0补偿与0.5补偿对比测试结果/测试图片/Texture2D/zhenhai_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WriteCode-ChangeWorld/Tools/096dd8af8b9d1f501187ce62ab38e6be07f1518d/0x0a-碧蓝航线-静态立绘解密加密工具/解封包_0补偿与0.5补偿对比测试结果/测试图片/Texture2D/zhenhai_2.png -------------------------------------------------------------------------------- /0x0a-碧蓝航线-静态立绘解密加密工具/解封包_0补偿与0.5补偿对比测试结果/解包_+0.5_√_beierfasite_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WriteCode-ChangeWorld/Tools/096dd8af8b9d1f501187ce62ab38e6be07f1518d/0x0a-碧蓝航线-静态立绘解密加密工具/解封包_0补偿与0.5补偿对比测试结果/解包_+0.5_√_beierfasite_4.png -------------------------------------------------------------------------------- /0x0a-碧蓝航线-静态立绘解密加密工具/解封包_0补偿与0.5补偿对比测试结果/解包_+0.5_√_zhenhai_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WriteCode-ChangeWorld/Tools/096dd8af8b9d1f501187ce62ab38e6be07f1518d/0x0a-碧蓝航线-静态立绘解密加密工具/解封包_0补偿与0.5补偿对比测试结果/解包_+0.5_√_zhenhai_2.png -------------------------------------------------------------------------------- /0x0a-碧蓝航线-静态立绘解密加密工具/解封包_0补偿与0.5补偿对比测试结果/解包_+0_×_左下角与右下角有白色横线_zhenhai_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WriteCode-ChangeWorld/Tools/096dd8af8b9d1f501187ce62ab38e6be07f1518d/0x0a-碧蓝航线-静态立绘解密加密工具/解封包_0补偿与0.5补偿对比测试结果/解包_+0_×_左下角与右下角有白色横线_zhenhai_2.png -------------------------------------------------------------------------------- /0x0a-碧蓝航线-静态立绘解密加密工具/解封包_0补偿与0.5补偿对比测试结果/解包_+0_×_明显白线区域_beierfasite_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WriteCode-ChangeWorld/Tools/096dd8af8b9d1f501187ce62ab38e6be07f1518d/0x0a-碧蓝航线-静态立绘解密加密工具/解封包_0补偿与0.5补偿对比测试结果/解包_+0_×_明显白线区域_beierfasite_4.png -------------------------------------------------------------------------------- /0x0b-批量拖拽-文件后缀名重命名工具/Readme.md: -------------------------------------------------------------------------------- 1 | ## 批量拖拽-文件后缀名重命名工具 v1.1 2 | 3 | ### 功能 4 | 5 | + 支持拖拽文件夹/单个文件/多个文件到GUI界面进行批量重命名 6 | + 后缀名检测 7 | + 全中文/检测名单后缀名 -> 替换为默认后缀名 8 | + 部分中文后缀名 -> 去除中文 9 | + 无后缀名 -> 添加默认后缀名 10 | 11 | + 涉及修改的文件、未知类型文件会高亮显示 12 | + 检测名单和跳过名单独立为配置文件,提供exe打包 13 | + 支持自定义跳过的后缀名 14 | 15 | ![image-20240908162937224](./img/0.png) 16 | 17 | 18 | 19 | ### 后续可能的更新 20 | 21 | 22 | + 重命名并调用bandizip解压 23 | 24 | 25 | 26 | ### 其他 27 | pyinstaller打包命令 28 | 29 | ```bash 30 | pyinstaller --onefile --noconsole -n "FileRenamer v1.1.exe" filerenamer.py 31 | ``` 32 | 33 | -------------------------------------------------------------------------------- /0x0b-批量拖拽-文件后缀名重命名工具/exe/FileRenamer v1.1.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WriteCode-ChangeWorld/Tools/096dd8af8b9d1f501187ce62ab38e6be07f1518d/0x0b-批量拖拽-文件后缀名重命名工具/exe/FileRenamer v1.1.exe -------------------------------------------------------------------------------- /0x0b-批量拖拽-文件后缀名重命名工具/filerenamer.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | ''' 3 | @File : filerenamer.py 4 | @Time : 2024/09/06 21:29:04 5 | @Author : Coder-Sakura 6 | @Version : 1.1 7 | @Desc : None 8 | ''' 9 | 10 | # here put the import lib 11 | import wx 12 | import os 13 | import re 14 | import json 15 | from datetime import datetime 16 | from loguru import logger 17 | 18 | 19 | # 配置loguru日志 20 | logger.add("{time:YYYY-MM}.log",encoding="utf-8",enqueue=True) 21 | # 跳过名单 , "." 22 | skip_formats = [".mp4", ".avi", ".mov", ".mkv", ".flv", ".zip", ".rar", ".7z", ".tar.gz", ".EXE", ".exe",\ 23 | ".png", ".jpg", ".jpeg", ".gif", ".ico", ".psd", ".webp", ".mp3", ".wav", ".ass", ".lrc",\ 24 | ".vtt", ".WAV", ".html"] 25 | # 检测名单 , "" 26 | target_formats = [".zi", ".rr", ".r", ".ra"] 27 | # 无后缀名时默认添加的后缀名 28 | default_suffix = ".zip" 29 | configName = "config.json" 30 | if not os.path.exists(configName): 31 | with open(configName, "w", encoding="utf-8") as f: 32 | json.dump({"skip_formats":skip_formats, "target_formats":target_formats, "default_suffix":default_suffix},\ 33 | f, ensure_ascii=False, indent=4) 34 | 35 | 36 | class FileDrop(wx.FileDropTarget): 37 | def __init__(self, window): 38 | wx.FileDropTarget.__init__(self) 39 | # super(FileDropTarget, self).__init__() 40 | self.window = window 41 | 42 | def OnDropFiles(self, x, y, files): 43 | """处理拖放的文件""" 44 | self.window.WriteLog(f"============ 运行开始 ============\n") 45 | if files: 46 | for file in files: 47 | if os.path.isdir(file): 48 | self.window.WriteLog(f"识别到文件夹 - {file}\n") 49 | for r, ds, fs in os.walk(file): 50 | for f_ in fs: 51 | full_f_ = os.path.join(r,f_) 52 | self.window.WriteLog(f"识别到文件 - {full_f_}") 53 | self.window.RenameFile(full_f_) 54 | else: 55 | self.window.WriteLog(f"识别到文件 - {file}") 56 | self.window.RenameFile(file) 57 | 58 | # 返回True表示成功处理了拖放的文件 59 | self.window.WriteLog(f"============ 运行结束 ============\n") 60 | return True 61 | 62 | # 返回False表示没有文件被拖放 63 | self.window.WriteLog(f"============ 运行结束 ============\n") 64 | return False 65 | 66 | 67 | class DropFile(wx.Frame): 68 | # def __init__(self, parent, id, title): 69 | # wx.Frame.__init__(self, parent, id, title, size = (450, 400)) 70 | def __init__(self, *args, **kw): 71 | wx.Frame.__init__(self, *args, **kw) 72 | self.InitUI() 73 | self.InitConfig() 74 | 75 | def InitUI(self): 76 | self.title = "文件重命名工具 By Coder-Sakura" 77 | 78 | # 启用拖放 79 | self.text = wx.TextCtrl(self, -1, style = wx.TE_MULTILINE | wx.TE_RICH) 80 | self.text.SetBackgroundColour(wx.Colour(200, 200, 200)) 81 | dt = FileDrop(self) 82 | self.text.SetDropTarget(dt) 83 | 84 | # 设置窗口大小和位置 85 | self.Centre() 86 | self.SetSize((800, 400)) 87 | self.SetTitle(self.title) 88 | self.Show(True) 89 | 90 | def InitConfig(self): 91 | with open(configName, "r", encoding="utf-8") as f: 92 | self.configData = json.load(f) 93 | self.skip_formats = self.configData["skip_formats"] 94 | self.target_formats = self.configData["target_formats"] 95 | self.default_suffix = self.configData["default_suffix"] 96 | 97 | def WriteLog(self, text, level="info", color=""): 98 | now = datetime.now().strftime("%Y-%m-%d %H:%M:%S") 99 | start = self.text.GetInsertionPoint() # 获取当前文本位置 100 | attr = wx.TextAttr() 101 | attr.SetTextColour(wx.Colour(255, 0, 0)) # 红色 102 | attr.SetTextColour([wx.Colour(255, 0, 0) if color == "red" else wx.Colour(0, 0, 255) if color == "blue" else wx.Colour(0, 0, 0)][0]) # 红/蓝/黑 103 | self.text.WriteText(f"{now} {text}\n") 104 | self.text.SetStyle(start, start + len(now+text), attr) # 插入文本并应用样式 105 | 106 | log_level_method = getattr(logger, level, None) 107 | log_level_method(f"{now} {text}") 108 | 109 | def RenameFile(self, file_path): 110 | dir,fullfname = os.path.split(file_path) 111 | fname,ext = os.path.splitext(fullfname) 112 | 113 | # ================= 后缀名处理 ================= # 114 | # 跳过名单的后缀名 -> 跳过 115 | if ext in self.skip_formats: 116 | self.WriteLog(f"跳过文件 - {file_path}\n") 117 | return 118 | 119 | # 无后缀名 -> 添加默认后缀名 120 | if not ext and fullfname == fname: 121 | dst = os.path.join(dir, f"{fname}{self.default_suffix}") 122 | os.rename(file_path, dst) 123 | self.WriteLog(f"添加后缀名 - {dst}\n", level="success", color="blue") 124 | return 125 | 126 | # 全中文后缀名/检测名单的后缀名 -> 修改为默认后缀名 127 | if bool(re.match(r"^[\u4e00-\u9fa5]+$", ext[1:])) \ 128 | or ext in self.target_formats: 129 | dst = os.path.join(dir, f"{fname}{self.default_suffix}") 130 | os.rename(file_path, dst) 131 | self.WriteLog(f"重命名文件 - {dst}\n", level="success", color="blue") 132 | return 133 | 134 | # 部分中文后缀名 -> 去除中文 135 | if bool(re.findall(r"[\u4e00-\u9fa5]", ext[1:])): 136 | new_ext = re.sub(r"[\u4e00-\u9fa5]", "", ext) 137 | dst = os.path.join(dir, f"{fname}{new_ext}") 138 | os.rename(file_path, dst) 139 | self.WriteLog(f"重命名文件 - {dst}\n", level="success", color="blue") 140 | return 141 | 142 | self.WriteLog(f"未知类型 - {file_path}\n", level="warning", color="red") 143 | return 144 | 145 | 146 | app = wx.App() 147 | DropFile(None, -1, 'filedrop.py') 148 | app.MainLoop() -------------------------------------------------------------------------------- /0x0b-批量拖拽-文件后缀名重命名工具/img/0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WriteCode-ChangeWorld/Tools/096dd8af8b9d1f501187ce62ab38e6be07f1518d/0x0b-批量拖拽-文件后缀名重命名工具/img/0.png -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | ### 概述 2 | 3 | 这是一个收集各种各样工具或脚本并分享的仓库。 4 | 5 | 6 | 7 | ### 目前收集的脚本 8 | 9 | | 名称 | 描述 | 更新时间 | 10 | | ---------------------------------- | ------------------------------------------------------------ | -------------------------------------- | 11 | | 0x01-监控反代pixiv链接 | 将pixiv图片链接替换为可直接访问的反代链接 | 2021/2/14 | 12 | | 0x02-花火学园邀请码脚本 | 根据现有题库,自动匹配答题得出邀请码 | 目前站点禁止新用户注册 | 13 | | 0x03-iwara下载脚本 | iwara用户投稿视频/指定视频下载器 | 2022/3/11 | 14 | | 0x04-mzitu爬虫 | 妹子图全站下载爬虫 | 2021/2/14 | 15 | | 0x05-绅士小站 | 绅士小站爬虫 | 站点无法访问/无限期维护
2021/8/10 | 16 | | 0x06-pixiv动图合成 | pixiv动图合成脚本 | 2021/4/20 | 17 | | 0x07-绅士仓库 | 绅士仓库投稿抓取(不含下载)By [WolfBolin](https://github.com/wolfbolin) | 2021/6/30 | 18 | | 0x08-绅士小站2 | 绅士小站爬虫(MySQL+Aria2)By [WolfBolin](https://github.com/wolfbolin) | 2021/7/2 | 19 | | 0x09-pixiv2eagle | Pixiv插画tag导入Eagle | 2021/11/7 | 20 | | 0x0a-碧蓝航线-静态立绘解密加密工具 | 用于碧蓝航线静态立绘加密与解密的脚本 | 2022/1/4 | 21 | | 0x0b-批量拖拽-文件后缀名重命名工具 | 可以拖拽进行批量文件名后缀命名的GUI | 2024/9/8 | 22 | 23 | --------------------------------------------------------------------------------