├── .gitignore ├── .nojekyll ├── LICENSE └── docs ├── .nojekyll ├── App自动化 ├── App爬虫 │ ├── code_ │ │ ├── douban_.py │ │ └── get_sig_.py │ ├── images │ │ ├── search_sig.png │ │ ├── spider.webp │ │ ├── 新书速递.png │ │ ├── 新书速递charles.png │ │ ├── 添加代理.png │ │ └── 爬虫加密.webp │ ├── 我是如何开发App爬虫的.md │ └── 通用的App爬虫流程.md ├── readme.md ├── 抓包配置 │ └── readme.md ├── 环境搭建 │ └── readme.md └── 自动化框架 │ └── Appium │ └── xhs.md ├── CNAME ├── GUI ├── readme.md └── tk_数据库交互查询界面.md ├── Pypi包发布 └── readme.md ├── README.md ├── _coverpage.md ├── _navbar.md ├── _sidebar.md ├── ignore ├── PingFang Bold.ttf └── font.css ├── index.html ├── 作者 ├── h.jpg ├── 作者微信.jpg └── 微信打赏.jpg ├── 办公自动化 ├── email │ ├── images │ │ ├── MD示例图.jpg │ │ ├── PI_HTML.png │ │ ├── PI_TEXT.png │ │ ├── PI_附件视频.png │ │ ├── SERVER酱GET.png │ │ ├── SERVER酱通知.png │ │ ├── YAGMAIL_TEXT.png │ │ └── YAGMAIL_附件.png │ ├── readme.md │ ├── 微信推送.md │ ├── 邮件推送.md │ └── 钉钉推送.md ├── excel │ ├── data │ │ └── 数据准备csv.md │ ├── images │ │ └── 第三方库对比.png │ ├── pandas.md │ ├── polars.md │ ├── readme.md │ ├── 一些案例.md │ └── 常用功能.md ├── markdown │ └── markdown_.md ├── pdf │ └── readme.md ├── ppt │ ├── readme.md │ └── 常用功能.md ├── readme.md ├── word │ ├── images │ │ ├── 段落.png │ │ └── 蔡水根.jpg │ ├── readme.md │ ├── 一些案例.md │ └── 常用功能.md ├── 定时任务 │ └── 基本用法.md ├── 数据处理 │ ├── files │ │ ├── msyhbd.ttf │ │ ├── pic │ │ │ ├── 1.jpeg │ │ │ └── 1.jpg │ │ └── stopwords │ ├── images │ │ ├── 诠释词云.jpg │ │ └── 诠释词频.jpg │ ├── readme.md │ └── 图表展示 │ │ ├── readme.md │ │ ├── 词云.md │ │ └── 词频.md ├── 文件处理 │ ├── m3u8音视频拼接.md │ ├── python视频转音频.md │ ├── readme.md │ ├── 图像压缩.md │ ├── 图像拼接.md │ └── 字符串相似度匹配.md └── 自然语言处理 │ ├── readme.md │ ├── 文本翻译.md │ └── 音频转文字.md ├── 快速初始化 ├── installer.md └── readme.md ├── 数据库 ├── MongoDB │ ├── MongoDB.md │ ├── Python-MongoDB.md │ ├── images │ │ ├── Mongodb数据库工具.png │ │ └── 选择Mongodb版本.png │ ├── 安装MongoDB.md │ └── 实际应用.md ├── Mysql │ ├── Mysql.md │ ├── Orm.md │ ├── Python-Mysql.md │ ├── 安装Mysql.md │ ├── 底层结构.md │ └── 笑谈索引.md ├── Redis │ ├── Redis.md │ └── 安装Redis.md └── readme.md ├── 杂谈 ├── 可视化工具及安装.md └── 破解包 │ └── jetbrains无限重置插件.zip ├── 桌面自动化 ├── readme.md ├── 一些案例.md └── 快速初始化.md ├── 浏览器自动化 ├── pyppeteer │ ├── images │ │ ├── Chromium.png │ │ ├── book_douban.png │ │ ├── pyppeteer.png │ │ ├── 异常浏览器检测.png │ │ ├── 正常浏览器检测.png │ │ ├── 浏览器版本.png │ │ ├── 浏览器驱动版本.png │ │ ├── 绕过浏览器检测.png │ │ └── 豆瓣登陆成功.png │ ├── pyppeteer常用功能.md │ ├── 一些案例.md │ ├── 初始化pyppeteer.md │ ├── 编写自动下载表格程序.md │ └── 编写自动登录程序.md ├── readme.md ├── selenium │ ├── images │ │ ├── Chromium.png │ │ ├── 浏览器版本.png │ │ └── 浏览器驱动版本.png │ ├── selenium常用功能.md │ ├── 一些案例.md │ ├── 初始化selenium.md │ └── 编写自动下载表格程序.md ├── 常用工具 │ ├── CodeSheep.html │ ├── My.html │ └── readme.md └── 油猴插件 │ └── readme.md ├── 爬虫 ├── demo(抓取携程热门景点评论).md ├── m3u8音视频拼接.md ├── readme.md ├── 代理服务器.md ├── 工具简介.md ├── 攻破5秒盾.md └── 数据抓取入门.md ├── 调式和捕捉错误 ├── dostest.md ├── readme.md └── traceback.md ├── 软件 ├── readme.md ├── 影刀RPA │ ├── readme.md │ └── 天猫后台自动提报 │ │ ├── images │ │ ├── Snipaste_2021-04-02_14-29-17.png │ │ └── Snipaste_2021-04-02_14-29-52.png │ │ └── 天猫后台自动化提报操作文档.md └── 阿里RPA │ └── 阿里RPA.md └── 错误页面 └── _404.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Python template 3 | # Byte-compiled / optimized / DLL files 4 | __pycache__/ 5 | *.py[cod] 6 | *$py.class 7 | 8 | # C extensions 9 | *.so 10 | 11 | # Distribution / packaging 12 | .Python 13 | userData/ 14 | .idea/ 15 | build/ 16 | develop-eggs/ 17 | dist/ 18 | downloads/ 19 | eggs/ 20 | .eggs/ 21 | lib/ 22 | lib64/ 23 | parts/ 24 | sdist/ 25 | var/ 26 | wheels/ 27 | share/python-wheels/ 28 | *.egg-info/ 29 | .installed.cfg 30 | *.egg 31 | MANIFEST 32 | 33 | # PyInstaller 34 | # Usually these files are written by a python script from a template 35 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 36 | *.manifest 37 | *.spec 38 | 39 | # Installer logs 40 | pip-log.txt 41 | pip-delete-this-directory.txt 42 | 43 | # Unit test / coverage reports 44 | htmlcov/ 45 | .tox/ 46 | .nox/ 47 | .coverage 48 | .coverage.* 49 | .cache 50 | nosetests.xml 51 | coverage.xml 52 | *.cover 53 | *.py,cover 54 | .hypothesis/ 55 | .pytest_cache/ 56 | cover/ 57 | 58 | # Translations 59 | *.mo 60 | *.pot 61 | 62 | # Django stuff: 63 | *.log 64 | local_settings.py 65 | db.sqlite3 66 | db.sqlite3-journal 67 | 68 | # Flask stuff: 69 | instance/ 70 | .webassets-cache 71 | 72 | # Scrapy stuff: 73 | .scrapy 74 | 75 | # Sphinx documentation 76 | docs/_build/ 77 | 78 | # PyBuilder 79 | .pybuilder/ 80 | target/ 81 | 82 | # Jupyter Notebook 83 | .ipynb_checkpoints 84 | 85 | # IPython 86 | profile_default/ 87 | ipython_config.py 88 | 89 | # pyenv 90 | # For a library or package, you might want to ignore these files since the code is 91 | # intended to run in multiple environments; otherwise, check them in: 92 | # .python-version 93 | 94 | # pipenv 95 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 96 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 97 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 98 | # install all needed dependencies. 99 | #Pipfile.lock 100 | 101 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 102 | __pypackages__/ 103 | 104 | # Celery stuff 105 | celerybeat-schedule 106 | celerybeat.pid 107 | 108 | # SageMath parsed files 109 | *.sage.py 110 | 111 | # Environments 112 | .env 113 | .venv 114 | env/ 115 | venv/ 116 | ENV/ 117 | env.bak/ 118 | venv.bak/ 119 | 120 | # Spyder project settings 121 | .spyderproject 122 | .spyproject 123 | 124 | # Rope project settings 125 | .ropeproject 126 | 127 | # mkdocs documentation 128 | /site 129 | 130 | # mypy 131 | .mypy_cache/ 132 | .dmypy.json 133 | dmypy.json 134 | 135 | # Pyre type checker 136 | .pyre/ 137 | 138 | # pytype static type analyzer 139 | .pytype/ 140 | 141 | # Cython debug symbols 142 | cython_debug/ 143 | 144 | -------------------------------------------------------------------------------- /.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PY-GZKY/PythonAutomatedOfficeGuide/014509edfeff065588a1391c5a3e116976a40531/.nojekyll -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 PY-GZKY 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /docs/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PY-GZKY/PythonAutomatedOfficeGuide/014509edfeff065588a1391c5a3e116976a40531/docs/.nojekyll -------------------------------------------------------------------------------- /docs/App自动化/App爬虫/code_/douban_.py: -------------------------------------------------------------------------------- 1 | import base64 2 | import hashlib 3 | import hmac 4 | import time 5 | from urllib import parse 6 | 7 | import requests 8 | from requests.exceptions import ConnectionError 9 | 10 | 11 | class GetD(): 12 | def __init__(self): 13 | self.headers = { 14 | "User-Agent": "Rexxar-Core/0.1.3 api-client/1 com.douban.frodo/7.11.1(220) Android/25 product/VOG-AL00 vendor/HUAWEI model/VOG-AL00 rom/android network/wifi udid/c72cfb38a040b64521255795860f17a634090668 platform/AndroidPad nd/1 com.douban.frodo/7.11.1(220) Rexxar/1.2.151 platform/AndroidPad 1.2.151", 15 | } 16 | 17 | self.r = requests.session() 18 | 19 | def get_html(self, url): 20 | t_ = int(time.time()) 21 | 22 | params = { 23 | "tags": "", 24 | "refresh": 0, 25 | "selected_categories": {}, 26 | "start": 0, 27 | "count": 8, 28 | "udid": "c72cfb38a040b64521255795860f17a634090668", 29 | "rom": "android", 30 | "apikey": "0dad551ec0f84ed02907ff5c42e8ec70", 31 | "s": "rexxar_new", 32 | "channel": "Baidu_Market", 33 | "timezone": "Asia/Shanghai", 34 | "device_id": "c72cfb38a040b64521255795860f17a634090668", 35 | "os_rom": "android", 36 | "apple": "9efa6ba99021c3c98ef09c7dd7543653", 37 | # "icecream": "84ced86782ed487aca374defabfd29c5", 38 | # "mooncake": "17145441849e2d8e8e757360917238ea", 39 | "sugar": 46000, 40 | "loc_id": 108288, 41 | "_sig": self.get_sig(url=url, ts=t_), 42 | "_ts": t_, 43 | } 44 | try: 45 | req = self.r.get(url, params=params, headers=self.headers) 46 | if req.status_code == 200: 47 | req.encoding = req.apparent_encoding 48 | return req.json() 49 | return None 50 | except ConnectionError: 51 | return None 52 | 53 | def parse_json(self, json_data): 54 | print(json_data) 55 | 56 | def get_sig(self, url: str, ts: int, method='GET') -> str: 57 | """ 58 | :param url: api 59 | :param ts: 时间戳 60 | :param method: 请求方法(大写 GET POST) 61 | :return: 62 | """ 63 | url_path = parse.urlparse(url).path 64 | raw_sign = '&'.join([method.upper(), parse.quote(url_path, safe=''), str(ts)]) 65 | return base64.b64encode( 66 | hmac.new("bf7dddc7c9cfe6f7".encode(), raw_sign.encode(), hashlib.sha1).digest()).decode() 67 | 68 | def run(self): 69 | json_data = self.get_html(url="https://frodo.douban.com/api/v2/book/recommend") 70 | self.parse_json(json_data) 71 | 72 | 73 | if __name__ == '__main__': 74 | G = GetD() 75 | G.run() 76 | -------------------------------------------------------------------------------- /docs/App自动化/App爬虫/code_/get_sig_.py: -------------------------------------------------------------------------------- 1 | import hashlib 2 | import hmac 3 | import base64 4 | from urllib import parse 5 | API_SECRET_KEY = "bf7dddc7c9cfe6f7" 6 | 7 | def gen_sign(url: str, ts: int, method='GET') -> str: 8 | """ 9 | :param url: api 10 | :param ts: 时间戳 11 | :param method: 请求方法(大写 GET POST) 12 | :return: 13 | """ 14 | url_path = parse.urlparse(url).path 15 | raw_sign = '&'.join([method.upper(), parse.quote(url_path, safe=''), str(ts)]) 16 | print(raw_sign) 17 | return base64.b64encode(hmac.new(API_SECRET_KEY.encode(), raw_sign.encode(), hashlib.sha1).digest()).decode() 18 | 19 | # print(gen_sign(url="https://frodo.douban.com/api/v2/book/recommend", ts=1627624168)) -------------------------------------------------------------------------------- /docs/App自动化/App爬虫/images/search_sig.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PY-GZKY/PythonAutomatedOfficeGuide/014509edfeff065588a1391c5a3e116976a40531/docs/App自动化/App爬虫/images/search_sig.png -------------------------------------------------------------------------------- /docs/App自动化/App爬虫/images/spider.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PY-GZKY/PythonAutomatedOfficeGuide/014509edfeff065588a1391c5a3e116976a40531/docs/App自动化/App爬虫/images/spider.webp -------------------------------------------------------------------------------- /docs/App自动化/App爬虫/images/新书速递.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PY-GZKY/PythonAutomatedOfficeGuide/014509edfeff065588a1391c5a3e116976a40531/docs/App自动化/App爬虫/images/新书速递.png -------------------------------------------------------------------------------- /docs/App自动化/App爬虫/images/新书速递charles.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PY-GZKY/PythonAutomatedOfficeGuide/014509edfeff065588a1391c5a3e116976a40531/docs/App自动化/App爬虫/images/新书速递charles.png -------------------------------------------------------------------------------- /docs/App自动化/App爬虫/images/添加代理.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PY-GZKY/PythonAutomatedOfficeGuide/014509edfeff065588a1391c5a3e116976a40531/docs/App自动化/App爬虫/images/添加代理.png -------------------------------------------------------------------------------- /docs/App自动化/App爬虫/images/爬虫加密.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PY-GZKY/PythonAutomatedOfficeGuide/014509edfeff065588a1391c5a3e116976a40531/docs/App自动化/App爬虫/images/爬虫加密.webp -------------------------------------------------------------------------------- /docs/App自动化/App爬虫/我是如何开发App爬虫的.md: -------------------------------------------------------------------------------- 1 | > 对于爬虫工作者而言,如何有效的开发属于自己的爬虫项目? 2 | 3 | 我们都知道,爬虫的手段多种多样,随着时间推移还将出现更加牛逼的爬虫手段!! 4 | 5 | ![](./images/spider.webp) 6 | 7 | 我刚开始接触爬虫的时候也是从`pc端`开始获取网页源码进行解析,或者是通过网站后台返回的`ajax`接口来获取数据, 这都是非常常见的爬虫手段。 8 | 9 | 但是随着爬虫行业越来越被重视,反爬手段也在不断的加强,常规的爬虫手段实际上已经起不到作用的, 几年前搭建的`动态代理` `ip` 和随机 `ua请求头`就可以拿到数据的时代彻底过去,人们对数据的安全和重要性越来越重视, 可见`爬虫` 10 | 是一种多么重要的职业。 11 | 12 | ![](./images/爬虫加密.webp) 13 | 14 | 现在我们就来谈谈具体是怎么来开发我们的爬虫项目。 15 | 16 | ### 分析协议 17 | 18 | 这一步非常重要,也是分析目标网站的第一步,如果目标网站是`pc端`的产物, 我们一般会使用抓包工具来进行抓包或者使用`chrome`浏览器 自带的调试器进行网络请求的拦截, 19 | 以此来得出如何构造网络请求来得到我们所需的数据,然后用你喜欢的语言,比如`Python`来进行代码的编写。 20 | 21 | 一切似乎都在优雅的进行着,啪啪!! 为啥抓包工具抓不到有效的数据包? 为啥网页源码只有一段简单的`js`代码? 22 | `ajax` 后面拼接的一堆恶心的没有规律的字符串又是啥? 网页上正常显示的字体在网页源码中成了一个个的小框框? 23 | 24 | 这...... 别急, 你看到的这些反爬虫手段也只是一些经常出现的常规性手段, 越深入你会越觉得手段越来越多, 越来越变态。 25 | 26 | 上面我们说的都是围绕着`pc端`的`爬虫`和`反爬虫`, `pc端`总体上来说要比`App`更容易分析网络请求的`协议`, 我们可以很容易的进行网络拦截,但是处于pc端的网站也更容易设计一些变态的`反爬虫`, 27 | `验证码`,`js前端加密`,`代码混淆`这些也都是常见的手段。 28 | 29 | 所以对于一些`初级爬虫者`来说,通常很容易拿到请求的`url` 和 `参数`形式, 但就是找不到前端的加密方法和如何绕过的方案,可见而不可达呀!! 30 | 31 | `pc端反爬虫`虽然变态,但也不是铁板一块, 要不一大批爬虫工程师就该去卖煎饼果子了(卖煎饼果子其实很不错), 我们还是有办法解决这些难题的。但这就要看你的作为了, 破解只是时间问题罢了,这里我们不讨论具体的破解的方法和套路,这个话题太大 32 | !! 33 | 34 | 说了这么多,终于可以说回我们今天的标题了,` pc端`不行那我们就走`App`,也就是`移动端`, 网络协议一般来说不会有区别,所以如果一个网站同时存在pc端和app端, 并且在pc端碰到瓶颈,一时无法解决的时候,就要考虑去抓下app包, 35 | 甚至有一些还同时存在着微信小程序和支付宝小程序,这些都是你获取数据的战场, 并且作为一个不再是新手的爬虫告诉你,这些移动端的反爬措施要比pc端来的弱,不管是从参数加密还是验证码出现的频率 ,还是自动化工具的检测都比较弱,近几年`app爬虫` 36 | 的热潮一直都在攀升,移动端的使用人群也是越来越多,很多的网站也都在做`app`的转型, 这也算是大势所趋。由此可见`app爬虫`在爬虫界占有的地位,所以这个`app爬虫`系列也由此诞生!! 37 | 38 | 对于`app爬虫`而言,是否能够熟悉的使用各种爬虫和逆向工具就显得尤为重要, 工欲善其事必先利其器, 工具的使用很大程度上是决定你爬虫效率的最大因素, 下面就根据常见的工具来进行逐一介绍,算是列个`清单`,哈哈。 39 | 40 | ### Charles 41 | 42 | 使用爬虫开发你不得不知道的抓包工具,`Charles`不只是针对`app`的网络请求的拦截,在`pc端`的网站中也是适用的。 43 | 44 | 和一般的抓包工具一样,`Charles`支持`SSL`代理了,拦截`SSL`请求,网络调试,重发和编辑网络请求等操作。 45 | 46 | ### Fiddler 47 | 48 | 这个工具也是爬虫中非常主流的抓包工具,跟`Charles`有异曲同工之妙,我个人觉得区别不大,看个人的喜好倾向了。 49 | 50 | ### JEB 51 | 52 | 一款非常使用的安卓分析利器,底层是`Java`实现的,是处理`app反编译`的一把利刃。 53 | 54 | ### jadx 55 | 56 | 也是一款反编译的利器,支持命令行的操作,支持`apk`,`jar`等格式的文件,完成`apk反编译`得到源码。 57 | 58 | ### Apk helper 59 | 60 | 这是一种`apk查壳`的工具,传入apk文件,可以查看`apk`详情,应用权限,是否有使用加固,使用何种加固等等 61 | 62 | ### PKiD 63 | 64 | 另一种查壳的工具,比较小众,但是同样优秀 65 | 66 | ### HTTPCanary 67 | 68 | 运行在移动端上的抓包工具,并且非常主流,使用简单,上手流畅,功能强大 69 | 70 | ### ProxyDroid 71 | 72 | 全局代理了机器人,如果你抓不到`app`的数据包,它会是你的第一选择 73 | 74 | ### Xposed 75 | 76 | 一款可以在不修改 `APK` 的情况下影响程序运行的框架,比较底层,插件诸多,是一种 `Android hook` 的不错的方式 77 | 78 | ### JustTrustMe 79 | 80 | 绕过证书验证的利器,基于`Xposed`写的反`SSL Pinning`工具 81 | 82 | ### Frida 83 | 84 | 一款`Python`实现的`Hook`框架,在 `Android` 领域可谓是大有作为,支持脚本交互,注入工具`frida-server`,可以实现安卓跟系统的 `Hook` 交互,灵活性大 85 | 86 | > 再来看看`app`自动化常用的工具 87 | 88 | ### Appium 89 | 90 | 老牌的`app自动化测试`工具了,`pc` 有 `Selenium`,`app` 有 `Appium`,唉,一对难兄难弟。 91 | 92 | 当接口难以分析无从下手时,可以考虑`Appium`作为抓取的一种策略,结合 `Python` 脚本实现。 93 | 94 | ### AirTest 95 | 96 | 网易出品的一款自动化测试工具,基于`图像识别`和`poco控件`的`UI`自动化工具,功能也是极其的强大,在群控方面作用极佳 97 | 98 | 99 | 100 | > 这就是我平常使用app爬虫工具,列取了一些具有代表性的工具,除此之外还有很多功能强大的工具,爬虫这个领域太大了。 101 | 102 | > 好的工具能让你的爬虫效率提升一个档次,头发能不能保住可就说不准了。 103 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /docs/App自动化/App爬虫/通用的App爬虫流程.md: -------------------------------------------------------------------------------- 1 | 拦截 `App` 应用背后传输的数据包,首先我们需要用到抓包工具 2 | 3 | ## Charles 4 | 5 | 青花瓷是一把抓包利器,`Charles` 不只是针对`app` 的网络请求的拦截,在 `pc端` 的网站中也是适用的。 6 | 7 | 抓包工具配置 详见于 [抓包配置](../抓包配置/readme.md) 8 | 9 | ## 以"豆瓣"为例 10 | 11 | - 在逍遥模拟器的应用市场中安装 `豆瓣app` 12 | - `模拟器/真机` 设置中填写正确的`代理服务器`以及`端口`配置 13 | - 在电脑(假如你是 `Win10`) 和模拟器中 安装 `Charles` 证书 14 | 15 | 进来豆瓣图书模块、抓一下包看看 16 | 17 | --- 18 | ![新书速递](./images/新书速递.png) 19 | 20 | --- 21 | 22 | ![新书速递charles](./images/新书速递charles.png) 23 | 24 | 啊啊啊啊 !! 25 | 26 | 这豆瓣现在这么牛逼了吗,不是小白练手网站吗? 27 | 28 | 这么多加密参数了? 29 | 30 | 不是数据随便拿吗? 31 | 32 | ## 去除无意义参数 33 | 34 | 首先去除掉那些没有意义的参数,最小化参与请求的参数,没用的统统去掉。 35 | 36 | ```shell 37 | { 38 | "tags": "", 39 | "refresh": 0, 40 | "selected_categories": {}, 41 | "start": 0, 42 | "count": 8, 43 | "udid": "c72cfb38a040b64521255795860f17a634090668", 44 | "rom": "android", 45 | "apikey": "0dad551ec0f84ed02907ff5c42e8ec70", 46 | "s": "rexxar_new", 47 | "channel": "Baidu_Market", 48 | "timezone": "Asia/Shanghai", 49 | "device_id": "c72cfb38a040b64521255795860f17a634090668", 50 | "os_rom": "android", 51 | "apple": "9efa6ba99021c3c98ef09c7dd7543653", 52 | "icecream": "84ced86782ed487aca374defabfd29c5", 53 | "mooncake": "17145441849e2d8e8e757360917238ea", 54 | "sugar": 46000, 55 | "loc_id": 108288, 56 | "_sig": "vBgqG16HJEJK8hTAjyQFZmqO6+o=", 57 | "_ts": 1627624168, 58 | } 59 | ``` 60 | 这可不兴猜啊,不过大概估摸了一下: 61 | 62 | `udid && device_id` 都是设备号、是个常量, 63 | 64 | `os_rom && rom` 是操作系统, 65 | 66 | `mooncake && icecream` 无意义参数、去掉, 67 | 68 | `apikey && apple` 常量、固定不变, 69 | 70 | `loc_id` 这是模块id(这里是新书速递), 71 | 72 | `_ts` 时间戳(10位), 73 | 74 | `_sig` 参数,不用看,所有参数中需要破解的参数就是它了。 75 | 76 | ## 搜索 `_sig` 77 | 78 | 在 charles 中尝试搜索一下 _sig,发现都是在 params 中已经携带过来的。 79 | 一点生成 _sig 的痕迹都没有。 80 | 81 | ## 反编译 `豆瓣apk` 82 | 83 | 这里用到的反编译工具是 `jadx-gui` 84 | 85 | 直接搜索 `_sig` 并试图找到有关 `params` 请求参数的方法,最终定位到 其加密的类,代码如下: 86 | 87 | ![](./images/search_sig.png) 88 | 89 | ```shell 90 | public class ApiSignatureHelper { 91 | static Pair a(Request request) { 92 | if (request == null) { 93 | return null; 94 | } 95 | String header = request.header(com.douban.push.internal.api.Request.HEADER_AUTHORIZATION); 96 | if (!TextUtils.isEmpty(header)) { 97 | header = header.substring(7); 98 | } 99 | return a(request.url().toString(), request.method(), header); 100 | } 101 | 102 | public static Pair a(String str, String str2, String str3) { 103 | String decode; 104 | if (TextUtils.isEmpty(str)) { 105 | return null; 106 | } 107 | String str4 = FrodoApi.a().e.b; 108 | if (TextUtils.isEmpty(str4)) { 109 | return null; 110 | } 111 | StringBuilder sb = new StringBuilder(); 112 | sb.append(str2); 113 | String encodedPath = HttpUrl.parse(str).encodedPath(); 114 | if (encodedPath == null || (decode = Uri.decode(encodedPath)) == null) { 115 | return null; 116 | } 117 | if (decode.endsWith("/")) { 118 | decode = decode.substring(0, decode.length() - 1); 119 | } 120 | sb.append(StringPool.AMPERSAND); 121 | sb.append(Uri.encode(decode)); 122 | if (!TextUtils.isEmpty(str3)) { 123 | sb.append(StringPool.AMPERSAND); 124 | sb.append(str3); 125 | } 126 | long currentTimeMillis = System.currentTimeMillis() / 1000; 127 | sb.append(StringPool.AMPERSAND); 128 | sb.append(currentTimeMillis); 129 | return new Pair<>(HMACHash1.a(str4, sb.toString()), String.valueOf(currentTimeMillis)); 130 | } 131 | } 132 | ``` 133 | 134 | 其中 `HMACHash1` 类如下: 135 | ```shell 136 | public class HMACHash1 { 137 | public static final String a(String str, String str2) { 138 | try { 139 | SecretKeySpec secretKeySpec = new SecretKeySpec(str.getBytes(), LiveHelper.HMAC_SHA1); 140 | Mac instance = Mac.getInstance(LiveHelper.HMAC_SHA1); 141 | instance.init(secretKeySpec); 142 | return Base64.encodeToString(instance.doFinal(str2.getBytes()), 2); 143 | } catch (Exception e) { 144 | e.printStackTrace(); 145 | return null; 146 | } 147 | } 148 | } 149 | ``` 150 | 151 | 152 | ## APP 签名秘钥 153 | 154 | 来到这里,对于 `HMACHash1.a` 中传入的 key 我一直不知道从何而来。 155 | 156 | 不行,先 Google 一波,终于发现一篇文章中有比较详细的介绍到这个 `key` 是怎么来的。 157 | 158 | > [https://blog.csdn.net/qq_23594799/article/details/108446352](https://blog.csdn.net/qq_23594799/article/details/108446352) 159 | 160 | 这回算是破案了。最后得到的这个秘钥值是 `bf7dddc7c9cfe6f7`,我们根据这个来进行 `sha1` 加密即可。 161 | 但是这都是java代码的实现啊,我们需要将其转换成 `Python` 版本。 162 | 163 | ## 用 Python 复写加密流程 164 | 165 | ```python 166 | import hashlib 167 | import hmac 168 | import base64 169 | from urllib import parse 170 | API_SECRET_KEY = "bf7dddc7c9cfe6f7" 171 | 172 | def gen_sign(url: str, ts: int, method='GET') -> str: 173 | """ 174 | :param url: api 175 | :param ts: 时间戳 176 | :param method: 请求方法(大写 GET POST) 177 | :return: 178 | """ 179 | url_path = parse.urlparse(url).path 180 | raw_sign = '&'.join([method.upper(), parse.quote(url_path, safe=''), str(ts)]) 181 | print(raw_sign) 182 | return base64.b64encode(hmac.new(API_SECRET_KEY.encode(), raw_sign.encode(), hashlib.sha1).digest()).decode() 183 | 184 | print(gen_sign(url="https://frodo.douban.com/api/v2/book/recommend", ts=1627624168)) 185 | ``` 186 | 187 | 得到 `_sig` 的值 188 | ```shell 189 | vBgqG16HJEJK8hTAjyQFZmqO6+o= 190 | ``` 191 | 192 | 集成代码 193 | 194 | ```python 195 | import base64 196 | import hashlib 197 | import hmac 198 | import time 199 | from urllib import parse 200 | 201 | import requests 202 | from requests.exceptions import ConnectionError 203 | 204 | 205 | class GetD(): 206 | def __init__(self): 207 | self.headers = { 208 | "User-Agent": "Rexxar-Core/0.1.3 api-client/1 com.douban.frodo/7.11.1(220) Android/25 product/VOG-AL00 vendor/HUAWEI model/VOG-AL00 rom/android network/wifi udid/c72cfb38a040b64521255795860f17a634090668 platform/AndroidPad nd/1 com.douban.frodo/7.11.1(220) Rexxar/1.2.151 platform/AndroidPad 1.2.151", 209 | } 210 | 211 | self.r = requests.session() 212 | 213 | def get_html(self, url): 214 | t_ = int(time.time()) 215 | 216 | params = { 217 | "tags": "", 218 | "refresh": 0, 219 | "selected_categories": {}, 220 | "start": 0, 221 | "count": 8, 222 | "udid": "c72cfb38a040b64521255795860f17a634090668", 223 | "rom": "android", 224 | "apikey": "0dad551ec0f84ed02907ff5c42e8ec70", 225 | "s": "rexxar_new", 226 | "channel": "Baidu_Market", 227 | "timezone": "Asia/Shanghai", 228 | "device_id": "c72cfb38a040b64521255795860f17a634090668", 229 | "os_rom": "android", 230 | "apple": "9efa6ba99021c3c98ef09c7dd7543653", 231 | # "icecream": "84ced86782ed487aca374defabfd29c5", 232 | # "mooncake": "17145441849e2d8e8e757360917238ea", 233 | "sugar": 46000, 234 | "loc_id": 108288, 235 | "_sig": self.get_sig(url=url, ts=t_), 236 | "_ts": t_, 237 | } 238 | try: 239 | req = self.r.get(url, params=params, headers=self.headers) 240 | if req.status_code == 200: 241 | req.encoding = req.apparent_encoding 242 | return req.json() 243 | return None 244 | except ConnectionError: 245 | return None 246 | 247 | def parse_json(self, json_data): 248 | print(json_data) 249 | 250 | def get_sig(self, url: str, ts: int, method='GET') -> str: 251 | """ 252 | :param url: api 253 | :param ts: 时间戳 254 | :param method: 请求方法(大写 GET POST) 255 | :return: 256 | """ 257 | url_path = parse.urlparse(url).path 258 | raw_sign = '&'.join([method.upper(), parse.quote(url_path, safe=''), str(ts)]) 259 | return base64.b64encode( 260 | hmac.new("bf7dddc7c9cfe6f7".encode(), raw_sign.encode(), hashlib.sha1).digest()).decode() 261 | 262 | def run(self): 263 | json_data = self.get_html(url="https://frodo.douban.com/api/v2/book/recommend") 264 | self.parse_json(json_data) 265 | 266 | 267 | if __name__ == '__main__': 268 | G = GetD() 269 | G.run() 270 | ``` 271 | 272 | ## 嘿嘿 273 | 这样一个比较通用的 app 爬虫就搞定了。 274 | 275 | 现在豆瓣都开始做反爬虫了吗? 276 | -------------------------------------------------------------------------------- /docs/App自动化/readme.md: -------------------------------------------------------------------------------- 1 | > App 是当下最主要、最流行的应用方式,愈来愈多的企业都在app应用上下大功夫,甚至 2 | > 摒弃了网页端,这是大势所趋。 3 | 4 | ### App爬虫 5 | 如果一个网站的数据加密过于复杂一时无法下手,而刚好该平台有其 App 应用程序, 6 | 我们不妨改变思路从 App 入手。 7 | 8 | 事实证明,普遍的 App 应用 (包括小程序、h5) 加密措施相比于 pc 端应用较弱。 9 | 10 | 11 | ### 自动化工具 12 | 13 | 现在的 rpa 工具/软件 开始支持 App 自动化操作,这可能是个好消息。 14 | 15 | 这大大减少了环境搭建的难度,让没有编程基础的小白也能开始愉快的玩耍。 16 | 17 | 但,rpa对移动设备的支持(包括ios、安卓机版本)极度的有限 18 | 19 | 20 | ### App群控 -------------------------------------------------------------------------------- /docs/App自动化/抓包配置/readme.md: -------------------------------------------------------------------------------- 1 | ## Charles 2 | 3 | ### windows 4 | #### 安装证书 5 | ### `help` > `SSLProxying` > `Install Charles root Certificate` > `本地` > `受信任的` 6 | 7 | --- 8 | 9 | #### 设置所有SSL代理 10 | `Proxy` > `SSL Proxying Settings` > 勾选 `Enable SSL Proxying` > `Add` > `Host:*,Port:443` > `ok` 11 | 12 | --- 13 | #### android(7.0以下) 14 | 15 | ### `wifi` > `长按` > `修改网络` > `高级选项` > `代理` > `手动` > `主机ip,charles端口` 16 | 17 | --- 18 | #### 证书 19 | - 访问 `chls.pro/ssl` 安装 20 | - 证书下载后手机`设置` > `安全` > `安装证书` 21 | --- 22 | 23 | #### iphone 24 | 25 | - 访问 `chls.pro/ssl` 安装 26 | - `设置` > `通用` > `关于本机` > `信任证书设置` > `设置相应证书` 27 | 28 | --- 29 | ## Mitmproxy 30 | ### windows 31 | `pip install mitmproxy` 32 | 33 | #### `windows` 不支持 `mitmproxy` 34 | 可以用 `mitmweb` 代替 35 | 36 | --- 37 | 38 | #### 安装证书 39 | ```shell 40 | cmd >mitmdump 41 | C:\Users\Administrator\.mitmproxy > 双击 mitmproxy-ca.p12 安装 42 | ``` 43 | 44 | --- 45 | 46 | ### android 47 | 48 | ### `wifi` > `长按` > `修改网络` > `高级选项` > `代理` > `手动` > `主机ip,mitmdump 端口` 49 | 50 | --- 51 | 52 | #### 证书安装 53 | - 访问 `mitm.it` 安装 54 | - 证书下载后手机 `设置` > `安全` > `安装证书` 55 | 56 | -------------------------------------------------------------------------------- /docs/App自动化/环境搭建/readme.md: -------------------------------------------------------------------------------- 1 | ## 模拟器 2 | 对比真机而言, 模拟器通常可以做到更多。这里推荐使用 `逍遥/MuMu` 模拟器。 3 | ```shell 4 | 夜神模拟器 adb connect 127.0.0.1:62001 5 | 逍遥模拟器 adb connect 127.0.0.1:21503 6 | mumu模拟器 adb connect 127.0.0.1:7555 7 | ``` 8 | ```shell 9 | adb connect 127.0.0.1 # 默认连接本地 5555 端口 10 | ``` 11 | 12 | 13 | ## 安装 sdk && jdk && node 14 | 15 | ## 安装 appium && aritest 16 | 17 | 这是两款比较主流的 `app` 自动化工具。 18 | 19 | ## 安装 charles && mitmproxy 20 | 21 | 这是两款比较主流的 `中间人抓包` 工具。 22 | 23 | -------------------------------------------------------------------------------- /docs/App自动化/自动化框架/Appium/xhs.md: -------------------------------------------------------------------------------- 1 | > 对于 `app` 自动化来说,`appium` 是必须知道的一个自动化测试框架。 2 | 3 | 值得一提的是 `appium` 环境搭建比 `Airtest` 要略显麻烦些。详见`环境搭建章节` 4 | 5 | 这里假设你的 `appium` 已经安装完毕。 6 | > 开始使用 Appium 7 | > 8 | ### 以小某书App为例 9 | 10 | ```shell 11 | DRIVER_SERVER = 'http://127.0.0.1:4723/wd/hub' 12 | DESIRED_CAPS = { 13 | "platformName": "Android", 14 | "deviceName": "HUAWEI P30 Pro", 15 | "appPackage": "com.xingin.xhs", 16 | "appActivity": ".activity.SplashActivity", 17 | "noReset": "True", 18 | "unicodeKeyboard": "True", 19 | "resetKeyboard": "True" 20 | } 21 | 22 | ``` 23 | 首先 `appium服务`的默认端口是 `4723`,当然如果多开的话可以是`n+2` 这样往累加。 24 | 25 | `DESIRED_CAPS` 指明了我们的连接信息。 26 | 这里来说: 27 | - `platformName`: 平台名称,这里是`安卓` 28 | - `deviceName`: 设备名称 29 | - `appPackage`: `app`包名 30 | - `appActivity`: `app activity` 31 | - `noReset`: 是否`重置` 32 | - `unicodeKeyboard`: 键盘`编码` 33 | 34 | 初始化代码如下 35 | ```python 36 | # -*- coding:utf-8 -*- 37 | import random 38 | import re 39 | import math 40 | from time import sleep 41 | from appium import webdriver 42 | from appium.webdriver.common.touch_action import TouchAction 43 | from selenium.webdriver.common.by import By 44 | from selenium.webdriver.support.ui import WebDriverWait 45 | from selenium.webdriver.support import expected_conditions as EC 46 | from selenium.common.exceptions import NoSuchElementException, TimeoutException, WebDriverException 47 | 48 | 49 | DRIVER_SERVER = 'http://127.0.0.1:4723/wd/hub' 50 | DESIRED_CAPS = { 51 | "platformName": "Android", 52 | "deviceName": "HUAWEI P30 Pro", 53 | "appPackage": "com.xingin.xhs", 54 | "appActivity": ".activity.SplashActivity", 55 | "noReset": "True", 56 | "unicodeKeyboard": "True", 57 | "resetKeyboard": "True" 58 | } 59 | 60 | class CrawlRedBook(): 61 | def __init__(self): 62 | self.driver = webdriver.Remote(DRIVER_SERVER, DESIRED_CAPS) 63 | self.wait = WebDriverWait(self.driver, 6) 64 | self.flag = 0 65 | ``` 66 | 67 | 可以看到我结合了 `selenium` 的显式等待的方法,集成到了 `appium` 中。 68 | 69 | 不出意外的话(`pc`和`移动端`网络连接无误等),运行以上代码是可以正常工作的。 70 | 71 | #### `appium`获取当前屏幕的大小 72 | 73 | ```python 74 | def get_size(self): 75 | x = self.driver.get_window_size()['width'] 76 | y = self.driver.get_window_size()['height'] 77 | return (x, y) 78 | ``` 79 | 80 | #### `appium`滑动屏幕操作 81 | 82 | ```python 83 | def first_action(self,keys): 84 | # self.wait.until(EC.presence_of_element_located((By.ID, "com.xingin.xhs:id/l7"))).click() 85 | self.wait.until(EC.presence_of_element_located((By.ID, "com.xingin.xhs:id/a01"))).click() # 点击搜索框 86 | self.wait.until(EC.presence_of_element_located((By.ID, "com.xingin.xhs:id/ar4"))).send_keys(keys) # 输入关键字 87 | sleep(1) 88 | # self.driver.press_keycode(66) # 回车键 89 | self.wait.until(EC.presence_of_element_located((By.ID, "com.xingin.xhs:id/ar6"))).click() # 回车键 90 | count = 0 91 | l = self.get_size() 92 | x1 = int(l[0] * 0.5) 93 | y1 = int(l[1] * 0.75) 94 | y2 = int(l[1] * 0.25) 95 | while 1: 96 | if 'THE END' in self.driver.page_source: 97 | break 98 | # TouchAction(self.driver).press(x=360, y=1100).wait(800).move_to(x=360, y=300).release().perform() 99 | self.driver.swipe(x1, y1, x1, y2) 100 | count += 1 101 | print(keys,"滑动次数: ", count) 102 | sleep(0.2) 103 | # TouchAction(self.driver).tap(x=684, y=1233).perform() 104 | ``` 105 | 106 | 这样就可以源源不断的向上滑动屏幕, 107 | `app` 后端的`接口`也就即时返回对应的 `response`, 108 | 我们也就能监听(`mitmproxy`)并获取其有效的数据。 109 | 110 | #### 关闭 `appium` 111 | ```python 112 | def close(self): 113 | self.driver.close_app() 114 | ``` 115 | 116 | #### 整体代码 117 | 118 | ```python 119 | # -*- coding:utf-8 -*- 120 | import random 121 | import re 122 | import math 123 | from time import sleep 124 | from appium import webdriver 125 | from appium.webdriver.common.touch_action import TouchAction 126 | from selenium.webdriver.common.by import By 127 | from selenium.webdriver.support.ui import WebDriverWait 128 | from selenium.webdriver.support import expected_conditions as EC 129 | from selenium.common.exceptions import NoSuchElementException, TimeoutException, WebDriverException 130 | 131 | 132 | DRIVER_SERVER = 'http://127.0.0.1:4723/wd/hub' 133 | DESIRED_CAPS = { 134 | "platformName": "Android", 135 | "deviceName": "HUAWEI P30 Pro", 136 | "appPackage": "com.xingin.xhs", 137 | "appActivity": ".activity.SplashActivity", 138 | "noReset": "True", 139 | "unicodeKeyboard": "True", 140 | "resetKeyboard": "True" 141 | } 142 | 143 | class CrawlRedBook(): 144 | def __init__(self): 145 | self.driver = webdriver.Remote(DRIVER_SERVER, DESIRED_CAPS) 146 | self.wait = WebDriverWait(self.driver, 6) 147 | self.flag = 0 148 | 149 | def get_size(self): 150 | x = self.driver.get_window_size()['width'] 151 | y = self.driver.get_window_size()['height'] 152 | return (x, y) 153 | 154 | def first_action(self,keys): 155 | # self.wait.until(EC.presence_of_element_located((By.ID, "com.xingin.xhs:id/l7"))).click() 156 | self.wait.until(EC.presence_of_element_located((By.ID, "com.xingin.xhs:id/a01"))).click() # 点击搜索框 157 | self.wait.until(EC.presence_of_element_located((By.ID, "com.xingin.xhs:id/ar4"))).send_keys(keys) # 输入关键字 158 | sleep(1) 159 | # self.driver.press_keycode(66) # 回车键 160 | self.wait.until(EC.presence_of_element_located((By.ID, "com.xingin.xhs:id/ar6"))).click() # 回车键 161 | 162 | count = 0 163 | l = self.get_size() 164 | x1 = int(l[0] * 0.5) 165 | y1 = int(l[1] * 0.75) 166 | y2 = int(l[1] * 0.25) 167 | while 1: 168 | if 'THE END' in self.driver.page_source: 169 | break 170 | # TouchAction(self.driver).press(x=360, y=1100).wait(800).move_to(x=360, y=300).release().perform() 171 | self.driver.swipe(x1, y1, x1, y2) 172 | count += 1 173 | print(keys,"滑动次数: ", count) 174 | sleep(0.2) 175 | # TouchAction(self.driver).tap(x=684, y=1233).perform() 176 | 177 | def close(self): 178 | self.driver.close_app() 179 | 180 | if __name__ == "__main__": 181 | pass 182 | ``` 183 | 184 | #### `mitmproxy`监听数据接口 185 | 186 | 假设这里我们需要监听小某书的`笔记列表`,可以新建 `get_id.py` 脚本如下: 187 | ```python 188 | # -*- coding:utf-8 -*- 189 | # import sys 190 | # sys.path.append('C:/Users/Administrator/AppData/Roaming/Python/Python37/site-packages') 191 | import json 192 | import re 193 | from urllib.parse import unquote 194 | 195 | def save_txt(filename, data): 196 | with open("./red_data/" + filename, "a", encoding="utf-8") as f: 197 | f.write(data + "\n") 198 | f.close() 199 | 200 | def response(flow): 201 | url = "https://edith.xiaohongshu.com/api/sns/v10/search/" 202 | if flow.request.url.startswith(url): 203 | print(flow.request.url) 204 | keyword = re.findall(r'search/notes\?keyword=(.*?)&', flow.request.url)[0] 205 | print("关键字: ", unquote(keyword)) 206 | 207 | text = flow.response.text 208 | items = json.loads(text) 209 | notes = items.get("data").get("items") 210 | 211 | for note in notes: 212 | item = {} 213 | try: 214 | item["user_id"] = note.get("note").get("user").get("userid") 215 | item["note_id"] = note.get("note").get("id") 216 | note_data_str = json.dumps(item) 217 | save_txt(f"{unquote(keyword)}.txt", note_data_str) 218 | except: 219 | pass 220 | ``` 221 | 222 | 223 | 启动监听脚本 224 | ```shell 225 | mitmdump -s ./get_id.py 226 | ``` 227 | 228 | 默认监听 `8080` 端口,当然你也可以自行指定端口号,你需要在你的`真机`或`模拟器`上填写正确的服务器地址和监听端口号。 229 | 230 | 这样我们就能将接口返回的数据初步解析并存储到本地文件中(或者入其他`待爬队列`)。 231 | 232 | ```shell 233 | {"user_id": "5d443c66000000001103429b", "note_id": "604cb35e0000000001026af6", } 234 | {"user_id": "5c433242000000000603fe7e", "note_id": "5e218aab00000000010001cc", } 235 | {"user_id": "5b17ff7af7e8b95acaf614d4", "note_id": "6018c335000000000100651b", } 236 | {"user_id": "5b63a7da4eacab1a72a526a5", "note_id": "607c49f6000000002103d3dc", } 237 | {"user_id": "59fc91c111be1055cd6fa82b", "note_id": "60dacd65000000000100a339", } 238 | {"user_id": "5c433242000000000603fe7e", "note_id": "5e58d939000000000100277b", } 239 | {"user_id": "5dda423800000000010036e8", "note_id": "5e2d344000000000010065d6", } 240 | {"user_id": "5ba6e9b823d0e40001c7e6e0", "note_id": "5fcd715e000000000100995b", } 241 | {"user_id": "6032103d000000000101efc4", "note_id": "6056e46f000000002103c662", } 242 | {"user_id": "5efbf7ec0000000001006d37", "note_id": "6024c4c8000000000102a1d5", } 243 | {"user_id": "5c091ec90000000007003966", "note_id": "5c0a01d2000000000801c8fd", } 244 | {"user_id": "5e8732f5000000000100ae8c", "note_id": "609e7fb40000000021038b68", } 245 | {"user_id": "5c433242000000000603fe7e", "note_id": "5e40f0b300000000010076e5", } 246 | {"user_id": "5fa3d0c5000000000100574a", "note_id": "60725f36000000002103b66a", } 247 | {"user_id": "606c67460000000001006aa4", "note_id": "60892cce000000000102b1f8", } 248 | {"user_id": "60b101530000000001009f2b", "note_id": "60d94aee000000002103be7b", } 249 | {"user_id": "6028c2620000000001005a0f", "note_id": "6094d72a00000000010294ba", } 250 | ``` 251 | 252 | 然后根据获取的 `user_id` 和 `note_id` 抓取相关的博主或笔记信息。 253 | 254 | > 代码仅供学习,切勿滥用。 255 | -------------------------------------------------------------------------------- /docs/CNAME: -------------------------------------------------------------------------------- 1 | python-automation.docs.tplan.cc -------------------------------------------------------------------------------- /docs/GUI/readme.md: -------------------------------------------------------------------------------- 1 | 除了运行脚本,有时候我们更希望能有一个界面,实现一个类似于`客户端`的操作。 2 | 3 | `Python` 的 `GUI` 库中,`Qt` 和 `Tk` 占据了半边天,它们都实现了一些有趣的交互。 4 | 5 | ```python 6 | # -*- coding: utf-8 -*- 7 | 8 | # Form implementation generated from reading ui file 'test.ui' 9 | # 10 | # Created by: PyQt5 UI code generator 5.11.2 11 | # 12 | # WARNING! All changes made in this file will be lost! 13 | 14 | from PyQt5 import QtCore, QtGui, QtWidgets 15 | import sys 16 | 17 | class Ui_Dialog(object): 18 | def setupUi(self, Dialog): 19 | Dialog.setObjectName("Dialog") 20 | Dialog.setWindowTitle("Pywinauto") 21 | Dialog.resize(505, 292) 22 | self.textEdit = QtWidgets.QTextEdit(Dialog) 23 | self.textEdit.setGeometry(QtCore.QRect(100, 40, 121, 31)) 24 | self.textEdit.setObjectName("textEdit") 25 | self.pushButton = QtWidgets.QPushButton(Dialog) 26 | self.pushButton.setGeometry(QtCore.QRect(80, 230, 81, 31)) 27 | font = QtGui.QFont() 28 | font.setFamily("微软雅黑") 29 | font.setPointSize(11) 30 | self.pushButton.setFont(font) 31 | self.pushButton.setObjectName("pushButton") 32 | self.radioButton = QtWidgets.QRadioButton(Dialog) 33 | self.radioButton.setGeometry(QtCore.QRect(100, 100, 41, 16)) 34 | self.radioButton.setObjectName("radioButton") 35 | self.checkBox = QtWidgets.QCheckBox(Dialog) 36 | self.checkBox.setGeometry(QtCore.QRect(100, 190, 121, 16)) 37 | self.checkBox.setObjectName("checkBox") 38 | self.label = QtWidgets.QLabel(Dialog) 39 | self.label.setGeometry(QtCore.QRect(30, 50, 54, 12)) 40 | self.label.setObjectName("label") 41 | self.radioButton_2 = QtWidgets.QRadioButton(Dialog) 42 | self.radioButton_2.setGeometry(QtCore.QRect(170, 100, 41, 16)) 43 | self.radioButton_2.setObjectName("radioButton_2") 44 | self.label_2 = QtWidgets.QLabel(Dialog) 45 | self.label_2.setGeometry(QtCore.QRect(30, 100, 61, 16)) 46 | self.label_2.setObjectName("label_2") 47 | self.label_3 = QtWidgets.QLabel(Dialog) 48 | self.label_3.setGeometry(QtCore.QRect(30, 190, 61, 16)) 49 | self.label_3.setObjectName("label_3") 50 | self.label_4 = QtWidgets.QLabel(Dialog) 51 | self.label_4.setGeometry(QtCore.QRect(30, 140, 61, 21)) 52 | self.label_4.setObjectName("label_4") 53 | self.comboBox = QtWidgets.QComboBox(Dialog) 54 | self.comboBox.setGeometry(QtCore.QRect(100, 140, 121, 22)) 55 | self.comboBox.setObjectName("comboBox") 56 | self.comboBox.setEditable(True) 57 | self.comboBox.addItem("") 58 | self.comboBox.addItem("") 59 | self.comboBox.addItem("") 60 | self.tableWidget = QtWidgets.QTableWidget(Dialog) 61 | self.tableWidget.setGeometry(QtCore.QRect(260, 40, 211, 221)) 62 | self.tableWidget.setColumnCount(2) 63 | self.tableWidget.setObjectName("tableWidget") 64 | self.tableWidget.setRowCount(0) 65 | 66 | self.retranslateUi(Dialog) 67 | self.comboBox.setCurrentIndex(-1) 68 | 69 | self.pushButton.clicked.connect(Dialog.close) 70 | QtCore.QMetaObject.connectSlotsByName(Dialog) 71 | 72 | value = [['张三', '20'], ['李四', '25']] 73 | for k in value: 74 | self.tableWidget.setRowCount(self.tableWidget.rowCount() + 1) 75 | rownumber = self.tableWidget.rowCount() 76 | for i in range(2): 77 | newItem = QtWidgets.QTableWidgetItem(k[i]) 78 | self.tableWidget.setItem(rownumber - 1, i, newItem) 79 | 80 | 81 | 82 | 83 | def retranslateUi(self, Dialog): 84 | _translate = QtCore.QCoreApplication.translate 85 | Dialog.setWindowTitle(_translate("Dialog", "Pywinauto")) 86 | self.pushButton.setText(_translate("Dialog", "关闭")) 87 | self.radioButton.setText(_translate("Dialog", "男")) 88 | self.checkBox.setText(_translate("Dialog", "我已阅读有关事项")) 89 | self.label.setText(_translate("Dialog", "姓名")) 90 | self.radioButton_2.setText(_translate("Dialog", "女")) 91 | self.label_2.setText(_translate("Dialog", "性别")) 92 | self.label_3.setText(_translate("Dialog", "注册事项")) 93 | self.label_4.setText(_translate("Dialog", "所在地")) 94 | self.comboBox.setItemText(0, _translate("Dialog", "广东省")) 95 | self.comboBox.setItemText(1, _translate("Dialog", "浙江省")) 96 | self.comboBox.setItemText(2, _translate("Dialog", "湖南省")) 97 | 98 | if __name__ == '__main__': 99 | app = QtWidgets.QApplication(sys.argv) 100 | MainWindow = QtWidgets.QMainWindow() 101 | ui = Ui_Dialog() 102 | ui.setupUi(MainWindow) 103 | MainWindow.show() 104 | sys.exit(app.exec_()) 105 | ``` -------------------------------------------------------------------------------- /docs/GUI/tk_数据库交互查询界面.md: -------------------------------------------------------------------------------- 1 | 2 | ### 与数据库的交互界面 3 | 基于 `Python tkinter` 和 `MySQL` 数据库的一个简单查询系统 4 | 5 | ### `tk_Interface.py` 6 | ```python 7 | # coding:utf-8 8 | import tkinter as tk 9 | from tkinter import * 10 | from tkinter import ttk 11 | import tkinter.messagebox 12 | 13 | try: 14 | from mysql_db import Mysql 15 | except: 16 | pass 17 | 18 | # 初始化 19 | window = tk.Tk() 20 | window.title('Data Query !') 21 | 22 | window.geometry('1500x800') 23 | 24 | l = tk.Label(window, text='请输入关键字查询', font=('Yahei', 11), width=40, height=2) 25 | l.pack() 26 | 27 | input_button = tk.Entry(window, show=None, font=('Yahei', 12), width=32) # 明文形式 28 | input_button.pack() 29 | 30 | tk.Label(window, font=('Yahei', 8), width=20, height=1).pack() # 过渡 31 | 32 | m_listbox_var = tk.StringVar() 33 | 34 | 35 | # 数据展示列表 36 | def listbox(temp_list): 37 | m_listbox_var.set('') 38 | yscrolly = tk.Scrollbar(window) 39 | yscrolly.pack(side=tk.RIGHT, fill=Y) 40 | 41 | m_list = tk.Listbox(window, font=('Arial', 11), listvariable=m_listbox_var, selectmode=tk.BROWSE, width=300, 42 | height=150, yscrollcommand=yscrolly.set) 43 | for item in temp_list: 44 | m_list.insert(tk.END, item) 45 | m_list.pack(fill=tk.BOTH) 46 | yscrolly.config(command=m_list.yview) 47 | 48 | 49 | def get_sql(): 50 | M = Mysql("test") 51 | data_list = M.query_data("law_content") 52 | 53 | value = input_button.get().strip() 54 | if value == '': 55 | tkinter.messagebox.showwarning(title='warning', message='请输入关键字') 56 | else: 57 | lst = [] 58 | for data in data_list: 59 | fenci_words_list = [i for i in data[3].split(' ') if i != ''] 60 | if value in fenci_words_list: 61 | lst.append(data[1] + ' ' + data[2]) 62 | else: 63 | # tkinter.messagebox.showwarning(title='Sorry', message='抱歉,查询无果') 64 | listbox(['抱歉,查询无果']) 65 | if len(lst) > 0: 66 | listbox(lst) 67 | 68 | 69 | B = tk.Button(window, text="查询", font=('Yahei', 10), width=10, height=1, command=get_sql) 70 | B.pack() 71 | 72 | tk.Label(window, font=('Yahei', 8), width=20, height=1).pack() # 过渡 73 | 74 | # 启动 75 | window.mainloop() 76 | ``` 77 | 78 | ### `mysql_db.py` 79 | ```python 80 | # -*- coding:utf-8 -*- 81 | import pymysql 82 | import json 83 | 84 | class Mysql(): 85 | ''' 86 | mysql数据库的一些常用操作 87 | ''' 88 | 89 | def __init__(self, database_name): 90 | 91 | self.db = pymysql.connect(host='127.0.0.1', user='root', password='', port=3306, db=database_name) 92 | self.cursor = self.db.cursor() 93 | 94 | # def create_database(self,database_name): 95 | # sql = "CREATE DATABASE {database_name} DEFAULT CHARACTER SET utf8".format(database_name=database_name) 96 | # self.cursor.execute(sql) 97 | 98 | def create_table(self, table_name): 99 | 100 | sql = f'CREATE TABLE IF NOT EXISTS {table_name} (id VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL, age INT NOT NULL, PRIMARY KEY (id))' 101 | self.cursor.execute(sql) 102 | 103 | def insert_data(self, table_name, data): 104 | 105 | sql = f'INSERT INTO {table_name}(id, content_name, content,fenci_words,key_words) values(%s, %s, %s, %s, %s)' 106 | for i in data: 107 | try: 108 | self.cursor.execute(sql, (i["id"], i["content_name"], i["content"], i["fenci_words"], i["key_words"])) 109 | self.db.commit() 110 | print("ok .......") 111 | except: 112 | self.db.rollback() 113 | 114 | def query_data(self, table_name): 115 | data_list = [] 116 | sql = f'SELECT * FROM {table_name}' 117 | 118 | try: 119 | self.cursor.execute(sql) 120 | # 全部数据列表 121 | results = self.cursor.fetchall() 122 | for row in results: 123 | data_list.append(row) 124 | return data_list 125 | except: 126 | print('Error') 127 | 128 | def delete_data(self, table_name, condition): 129 | sql = f"DELETE FROM {table_name} WHERE {condition}" # 删除条件 130 | try: 131 | self.cursor.execute(sql) 132 | self.db.commit() 133 | except: 134 | self.db.rollback() 135 | 136 | def update_data(self, table_name, data): 137 | keys = ', '.join(data.keys()) 138 | values = ', '.join(['%s'] * len(data)) 139 | 140 | sql = f"INSERT INTO {table_name} ({keys}) VALUES ({values}) ON DUPLICATE KEY UPDATE" 141 | update = ','.join([" {key} = %s".format(key=key) for key in data]) 142 | sql += update 143 | try: 144 | if self.cursor.execute(sql, tuple(data.values()) * 2): 145 | print('Successful') 146 | self.db.commit() 147 | except: 148 | print('Failed') 149 | self.db.rollback() 150 | 151 | def __del__(self): 152 | self.db.close() 153 | 154 | if __name__ == "__main__": 155 | M = Mysql("test") 156 | data_list = M.query_data("law_content") 157 | for data in data_list: 158 | print(data) 159 | 160 | # ccc = input("请输入关键字:") 161 | # 162 | # select_result = [] 163 | # for data in data_list: 164 | # fenci_words_list = [i for i in data[3].split(' ') if i != ''] 165 | # if ccc in fenci_words_list: 166 | # print("Content:",data[1]) 167 | ``` 168 | 169 | ### 数据爬虫更新界面 170 | ```python 171 | 172 | ``` -------------------------------------------------------------------------------- /docs/Pypi包发布/readme.md: -------------------------------------------------------------------------------- 1 | 2 | ## 依赖 3 | ```shell 4 | pip install setuptools 5 | pip install twine 6 | ``` 7 | 8 | ## 打包上传 9 | ```shell 10 | python setup.py sdist 11 | twine upload dist/* 12 | ``` 13 | 14 | ## 安装 15 | ```shell 16 | pip install package_name 17 | ``` 18 | 19 | ## setup.py demo 20 | ```python 21 | from os import path as os_path 22 | from setuptools import setup 23 | 24 | import haipproxy 25 | 26 | this_directory = os_path.abspath(os_path.dirname(__file__)) 27 | 28 | # 读取文件内容 29 | def read_file(filename): 30 | with open(os_path.join(this_directory, filename), encoding='utf-8') as f: 31 | long_description = f.read() 32 | return long_description 33 | 34 | # 获取依赖 35 | def read_requirements(filename): 36 | return [line.strip() for line in read_file(filename).splitlines() 37 | if not line.startswith('#')] 38 | 39 | setup( 40 | name='haipproxy', # 包名 41 | python_requires='>=3.4.0', # python环境 42 | version=haipproxy.__version__, # 包的版本 43 | description="High aviariable proxy pool client for crawlers.", # 包简介,显示在PyPI上 44 | long_description=read_file('README.md'), # 读取的Readme文档内容 45 | long_description_content_type="text/markdown", # 指定包文档格式为markdown 46 | author="Resolvewang", # 作者相关信息 47 | author_email='resolvewang@foxmail.com', 48 | url='https://github.com/SpiderClub/haipproxy', 49 | # 指定包信息,还可以用find_packages()函数 50 | packages=[ 51 | 'haipproxy', 52 | 'haipproxy.client', 53 | 'haipproxy.utils' 54 | ], 55 | install_requires=read_requirements('requirements.txt'), # 指定需要安装的依赖 56 | include_package_data=True, 57 | license="MIT", 58 | keywords=['proxy', 'client', 'haipproxy'], 59 | classifiers=[ 60 | 'Intended Audience :: Developers', 61 | 'License :: OSI Approved :: MIT License', 62 | 'Natural Language :: English', 63 | 'Programming Language :: Python :: 3.4', 64 | 'Programming Language :: Python :: 3.5', 65 | 'Programming Language :: Python :: 3.6', 66 | ], 67 | ) 68 | ``` -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | ### 《 启发式 `Python` 自动化办公 》 2 | 3 | https://python-automation.docs.tplan.cc 4 | 5 | > 本文档是作者从事 Python 以来的学习历程, 6 | 重点是 Python 在自动化方面的一些主要的应用场景。 7 | 8 | >希望能给予读者一份比较全面的自动化文档、一个比较舒适的阅读环境。 9 | 如果本文档对你有所帮助,请给予支持! 10 | 11 | ### 关于作者 12 | 13 | > 目前坐标广州,从事 `Python爬虫/开发` 多年。 14 | 15 | ## 为什么会写这个系列 16 | 17 | 现如今,谁还在手动重复的操作一些机械性的工作,拿出几天的时间学习`python`基础和自动化办公,每天至少可以减少`70%`的工作量,当你的工作越依赖于程序自动化的时候,也就意味着你的工作效率越高! 18 | 19 | 诸如`excel`表和`word`文档也自带着一些宏操作,也有一些已经封装好的函数供我们使用,为什么还要使用`python自动化`,又为什么一定是`python`? 20 | 21 | 第一,`excel`的宏操作和函数具有一定的局限性,而编程语言具有很高的扩展性,至少`python`看来是的,并且将一切手动的工作程序化,效率非常高! 22 | 23 | 至于为什么是`python`,明人不敲暗码,简单易上手啊!! 特别是对于不深入了解`python`的小白,甚至是完全不懂`python`的新手都非常的友好👬。 24 | 25 | 26 | 现在在一些技术社区也有不少的自动化的文章,但是终究还是比较零散,一些教程的覆盖面也是比较窄,对于新手来说并不能得到系统的学习,所以就有了这个系列的文章,我会努力覆盖到编程自动化的各个方面,让新手和小白有一个学习自动化的系列化文章,有点狂了啊!! 27 | 28 | 我这里列出了一个目录,往后也会新增作出更改。 29 | 30 | 包括但不限于`excel自动化`,`word自动化`,`ppt自动化`,`pdf自动化`,`email自动化`,`网页自动化`,`app自动化`,`文件处理自动化`,数据处理,`爬虫`自动化, 定时任务,`gui界面`等等 31 | 32 | 我将给出对应的源码到 `github` 地址来方便读者阅读学习,毕竟是自动化为主题的系列为教程,所以我不会去写`python`的一些基础操作。 33 | 34 | 当然必须声明的地方我会进行一些介绍,所以在此之前,我建议你先用一周左右的时间进行`python`的基础学习, 35 | 况且学习资源非常多,`python`官方文档就是非常好的学习资源,了解`python自动化`在工作中的具体应用场景。 36 | 37 | 再回头阅读此文档的时候则会更加轻松,虽然此文档不会涉及一些非常规的高深代码。 38 | 39 | 这个过程将会无比的美妙,会让你感觉到有所成就!! 40 | 41 | 42 | ### Python 自动化合集 43 | 44 | 如果你正从事 `Python` 自动化开发相关的工作, 那么此文档很可能对你有所帮助。 45 | 46 | 笔者将尽自己所能,把平时的工作经验以及所思所想毫无保留的分享至此,希望能对你有帮助!! 47 | 48 | 49 | ### 联系作者 50 | - 如果你有任何关于此文档的问题或者`Python自动化`相关的疑问,请添加作者微信并注明 `Python自动化` 51 | 52 | 53 | ![作者微信](./作者/作者微信.jpg) 54 | 55 | [comment]: <> (> 创作不易,如果你觉得此文档对你有所帮助,请作者喝杯咖啡吧 !!) 56 | 57 | [comment]: <> (![打赏码](作者/微信打赏.jpg ':size=18%')) 58 | 59 | ### 参与贡献 60 | - 如果您对本项目有任何建议或发现文中内容有误的,欢迎提交 `issues` 进行指正。 61 | - 对于文中我没有涉及到知识点,欢迎提交 `PR`。 62 | 63 | ### 转载须知 64 | - 转载需要添加作者微信(上方二维码),请备注:`转载`。 65 | - 转载时须标注转载来源: `文档`+`作者` 66 | 67 | 68 | > 文档中代码仅供学习,请勿商用,作者保留著作权。 69 | -------------------------------------------------------------------------------- /docs/_coverpage.md: -------------------------------------------------------------------------------- 1 | 2 | ## 启发式 `Python` 自动化办公 3 | 4 | 本文档是作者从事 Python 以来的学习历程, 5 | 重点是 Python 在自动化方面的一些主要的应用场景。 6 | 7 | 8 | 如果本文档对你有所帮助,请给予支持!持续维护中 :laughing: 9 | 10 | 12 | 13 | [GitHub](https://github.com/py-gzky/) 14 | [开始阅读](README.md) 15 | -------------------------------------------------------------------------------- /docs/_navbar.md: -------------------------------------------------------------------------------- 1 | 2 | * [web开发](https://django-fastapi.docs.tplan.cc) 3 | * [作者](https://resume.tplan.cc) 4 | 5 | [comment]: <> (* [:cn:](/zh-cn/)) -------------------------------------------------------------------------------- /docs/_sidebar.md: -------------------------------------------------------------------------------- 1 | * [快速初始化](快速初始化/readme.md) 2 | * [环境搭建](快速初始化/installer.md) 3 | 4 | * [办公自动化](办公自动化/readme.md) 5 | 6 | * [文件处理](办公自动化/文件处理/readme.md) 7 | * [m3u8音视频拼接](办公自动化/文件处理/m3u8音视频拼接.md) 8 | * [python视频转音频](办公自动化/文件处理/python视频转音频.md) 9 | * [字符串相似度匹配](办公自动化/文件处理/字符串相似度匹配.md) 10 | * [图像压缩](办公自动化/文件处理/图像压缩.md) 11 | * [图像拼接](办公自动化/文件处理/图像拼接.md) 12 | 13 | * [自然语言处理](办公自动化/自然语言处理/readme.md) 14 | * [文本翻译](办公自动化/自然语言处理/文本翻译.md) 15 | * [音频转文字](办公自动化/自然语言处理/音频转文字.md) 16 | 17 | * [excel](办公自动化/excel/readme.md) 18 | * [常用功能](办公自动化/excel/常用功能.md) 19 | * [一些案例](办公自动化/excel/一些案例.md) 20 | * [pandas](办公自动化/excel/pandas.md) 21 | * [polars](办公自动化/excel/polars.md) 22 | 23 | * [word](办公自动化/word/readme.md) 24 | * [常见功能](办公自动化/word/常用功能.md) 25 | * [一些案例](办公自动化/word/一些案例.md) 26 | 27 | * [ppt](办公自动化/ppt/readme.md) 28 | * [常用功能](办公自动化/ppt/常用功能.md) 29 | 30 | * [pdf](办公自动化/pdf/readme.md) 31 | 32 | * [markdown](办公自动化/pdf/markdown.md) 33 | 34 | * [推送通知](办公自动化/email/readme.md) 35 | * [邮件推送](办公自动化/email/邮件推送.md) 36 | * [钉钉推送](办公自动化/email/钉钉推送.md) 37 | * [微信推送](办公自动化/email/微信推送.md) 38 | 39 | * [定时任务](办公自动化/定时任务/基本用法.md) 40 | 41 | 42 | * [数据处理](办公自动化/数据处理/readme.md) 43 | * [图表展示](办公自动化/数据处理/图表展示/readme.md) 44 | * [词云](办公自动化/数据处理/图表展示/词云.md) 45 | * [词频](办公自动化/数据处理/图表展示/词频.md) 46 | 47 | * [数据库](数据库/readme.md) 48 | * Mysql 49 | * [安装Mysql](数据库/Mysql/安装Mysql.md) 50 | * [基础入门](数据库/Mysql/Mysql.md) 51 | * [Python交互](数据库/Mysql/Python-Mysql.md) 52 | * [底层结构](数据库/Mysql/底层结构.md) 53 | * [笑谈索引](数据库/Mysql/笑谈索引.md) 54 | * MongoDB 55 | * [安装MongoDB](数据库/MongoDB/安装MongoDB.md) 56 | * [基础入门](数据库/MongoDB/MongoDB.md) 57 | * [Python交互](数据库/MongoDB/Python-MongoDB.md) 58 | 59 | * Redis 60 | * [安装Redis](数据库/Redis/安装Redis.md) 61 | * [Redis](数据库/Redis/Redis.md) 62 | 63 | * [浏览器自动化](浏览器自动化/readme.md) 64 | * Selenium 65 | * [初始化selenium](浏览器自动化/selenium/初始化selenium.md) 66 | * [Selenium常用功能](浏览器自动化/selenium/selenium常用功能.md) 67 | * [一些案例](浏览器自动化/selenium/一些案例.md) 68 | 69 | * Pyppeteer 70 | * [初始化Pyppeteer](浏览器自动化/pyppeteer/初始化pyppeteer.md) 71 | * [Pyppeteer常用功能](浏览器自动化/pyppeteer/pyppeteer常用功能.md) 72 | * [一些案例](浏览器自动化/pyppeteer/一些案例.md) 73 | 74 | * 浏览器插件 75 | * [油猴插件](浏览器自动化/油猴插件/readme.md) 76 | 77 | * 常用工具 78 | * [需要自取](浏览器自动化/常用工具/readme.md) 79 | 80 | * [桌面自动化](桌面自动化/readme.md) 81 | * [快速初始化](桌面自动化/快速初始化.md) 82 | * [一些案例](桌面自动化/一些案例.md) 83 | 84 | * [App自动化](App自动化/readme.md) 85 | * [环境搭建](App自动化/环境搭建/readme.md) 86 | * [关于工具](App自动化/抓包配置/readme.md) 87 | * [App爬虫](App自动化/App爬虫/我是如何开发App爬虫的.md) 88 | * [通用的App爬虫流程](App自动化/App爬虫/通用的App爬虫流程.md) 89 | * [自动化框架](App自动化/自动化框架/Appium/xhs.md) 90 | 91 | * [数据爬虫](爬虫/readme.md) 92 | * [工具简介](爬虫/工具简介.md) 93 | * [数据抓取入门](爬虫/数据抓取入门.md) 94 | * [demo(抓取携程热门景点评论)](爬虫/demo(抓取携程热门景点评论).md) 95 | 96 | * [m3u8音视频拼接](爬虫/m3u8音视频拼接.md) 97 | * [代理服务器](爬虫/代理服务器.md) 98 | * [攻破5秒盾](爬虫/攻破5秒盾.md) 99 | 100 | * [GUI](GUI/readme.md) 101 | * [使用Tkinter](GUI/tk_数据库交互查询界面.md) 102 | 103 | * [调式和捕捉错误](调式和捕捉错误/readme.md) 104 | * [单元测试](调式和捕捉错误/dostest.md) 105 | 106 | * [发布Pypi包](Pypi包发布/readme.md) 107 | 108 | * [杂谈](杂谈/可视化工具及安装.md) -------------------------------------------------------------------------------- /docs/ignore/PingFang Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PY-GZKY/PythonAutomatedOfficeGuide/014509edfeff065588a1391c5a3e116976a40531/docs/ignore/PingFang Bold.ttf -------------------------------------------------------------------------------- /docs/ignore/font.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'PingFang'; 3 | src: url('PingFang Bold.ttf'); 4 | font-weight: bold; 5 | font-style: normal; 6 | } -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 启发式 Python 自动化办公 8 | 9 | 10 | 11 | 12 | 13 | 14 | 17 | 18 | 19 | 20 | 21 |
22 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 99 | 100 | 101 | 102 | 116 | 117 | 118 | 119 | -------------------------------------------------------------------------------- /docs/作者/h.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PY-GZKY/PythonAutomatedOfficeGuide/014509edfeff065588a1391c5a3e116976a40531/docs/作者/h.jpg -------------------------------------------------------------------------------- /docs/作者/作者微信.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PY-GZKY/PythonAutomatedOfficeGuide/014509edfeff065588a1391c5a3e116976a40531/docs/作者/作者微信.jpg -------------------------------------------------------------------------------- /docs/作者/微信打赏.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PY-GZKY/PythonAutomatedOfficeGuide/014509edfeff065588a1391c5a3e116976a40531/docs/作者/微信打赏.jpg -------------------------------------------------------------------------------- /docs/办公自动化/email/images/MD示例图.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PY-GZKY/PythonAutomatedOfficeGuide/014509edfeff065588a1391c5a3e116976a40531/docs/办公自动化/email/images/MD示例图.jpg -------------------------------------------------------------------------------- /docs/办公自动化/email/images/PI_HTML.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PY-GZKY/PythonAutomatedOfficeGuide/014509edfeff065588a1391c5a3e116976a40531/docs/办公自动化/email/images/PI_HTML.png -------------------------------------------------------------------------------- /docs/办公自动化/email/images/PI_TEXT.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PY-GZKY/PythonAutomatedOfficeGuide/014509edfeff065588a1391c5a3e116976a40531/docs/办公自动化/email/images/PI_TEXT.png -------------------------------------------------------------------------------- /docs/办公自动化/email/images/PI_附件视频.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PY-GZKY/PythonAutomatedOfficeGuide/014509edfeff065588a1391c5a3e116976a40531/docs/办公自动化/email/images/PI_附件视频.png -------------------------------------------------------------------------------- /docs/办公自动化/email/images/SERVER酱GET.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PY-GZKY/PythonAutomatedOfficeGuide/014509edfeff065588a1391c5a3e116976a40531/docs/办公自动化/email/images/SERVER酱GET.png -------------------------------------------------------------------------------- /docs/办公自动化/email/images/SERVER酱通知.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PY-GZKY/PythonAutomatedOfficeGuide/014509edfeff065588a1391c5a3e116976a40531/docs/办公自动化/email/images/SERVER酱通知.png -------------------------------------------------------------------------------- /docs/办公自动化/email/images/YAGMAIL_TEXT.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PY-GZKY/PythonAutomatedOfficeGuide/014509edfeff065588a1391c5a3e116976a40531/docs/办公自动化/email/images/YAGMAIL_TEXT.png -------------------------------------------------------------------------------- /docs/办公自动化/email/images/YAGMAIL_附件.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PY-GZKY/PythonAutomatedOfficeGuide/014509edfeff065588a1391c5a3e116976a40531/docs/办公自动化/email/images/YAGMAIL_附件.png -------------------------------------------------------------------------------- /docs/办公自动化/email/readme.md: -------------------------------------------------------------------------------- 1 | 2 | > 信息推送的方式有很多,邮箱、短信、社交类应用提醒等等 3 | 4 | 这里将使用 `邮件`、`钉钉`和`微信` 作为信息通知的对象载体,融入到我们日常的工作场景中。 5 | 6 | 原本的设想还会有`手机短信`和`电话`通知,考虑到现在的人类不会只看手机短信而不看微信,更何况`微信`和`钉钉`也更加贴近我们的办公环境,甚至成为了企业首选。 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /docs/办公自动化/email/微信推送.md: -------------------------------------------------------------------------------- 1 | 2 | ## `Server酱` 3 | 关于类如消息推送或机器人管理方面微信开放文档并没有给出简单易用的 `API` 接口, 这里使用的是 `Server酱` 服务推送通知。 4 | 5 | > [Server酱](http://sc.ftqq.com/?c=code) 6 | 7 | 你只需要`注册/登陆` `Server酱` 平台并扫码绑定微信公众号,对,`Server酱`本质是一个公众号,一个可以为你推送通知信息的公众号。 8 | 9 | 成功绑定后返回一个 `SCKEY`,相当于一个认证 `KEY`,用他们的话说,`SCKEY` 是非常重要的,所以请妥善保存,不要随便告诉别人。另外同样内容的消息一分钟只能发送一次,服务器只保留一周的消息记录。 10 | 11 | 具体接口如下: 12 | 13 | `https://sc.ftqq.com/[SCKEY].send` 14 | 15 | 它接受两个参数: 16 | - `text`:消息标题,最长为`256`,必填。 17 | - `desp`:消息内容,最长`64Kb`,可空,支持`MarkDown`。 18 | 19 | 20 | > PS: 此接口同时支持 `GET` 和 `POST` 请求。虽然我觉得并不合理,但它还是这么做了 21 | 22 | 23 | 所以你甚至没有必要编写代码,在浏览器地址栏键入`API`并传入必要参数也能发送通知,比如 24 | `https://sc.ftqq.com/[SCKEY].send?text=来自 PI 的问候` 25 | 26 | 不出所料,很快就收到了一条来自 `PI` 哥的问候 27 | 28 | ![](./images/SERVER酱GET.png) 29 | ![](./images/SERVER酱通知.png) 30 | 31 | ## `Python` 实现 32 | 33 | ```python 34 | # -*- coding: utf-8 -* 35 | # @Time : 2020/10/30 9:48 36 | 37 | import requests 38 | 39 | # 是否开启微信通知 40 | notice_enable = True 41 | # server酱key 42 | sckey = "SCU121832T2c26e......ac5f9b79369b576" 43 | # 信息主体 44 | desp = ''' 45 | **杭州天气** \n 46 | > 900度,西北风1级,空气良8009,相对温度673%\n 47 | > [![hello](https://images.pexels.com/photos/3369569/pexels-photo-3369569.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=750&w=1260)](https://www.dingtalk.com)\n 48 | > ###### 10点20分发布 [天气](https://www.dingtalk.com) \n 49 | ''' 50 | message = { 51 | "subject": "来自 PI 的问候", 52 | "content": desp 53 | } 54 | 55 | 56 | class SendMsg(object): 57 | def send(self, message): 58 | if not notice_enable: 59 | print("未开启微信通知") 60 | return 61 | url = f"https://sc.ftqq.com/{sckey}.send" 62 | r = requests.post(url, data={"text": f'{message["subject"]}', "desp": message["content"]}) 63 | if r.status_code == requests.codes.ok: 64 | print("微信通知发送成功") 65 | else: 66 | print("微信通知发送失败") 67 | 68 | 69 | S = SendMsg() 70 | S.send(message) 71 | ``` 72 | 73 | 74 | > 与浏览器地址栏相反,我们提交了一个 `POST` 请求,并且使用了`Markdown` 语法,大大增强了可读性。 75 | -------------------------------------------------------------------------------- /docs/办公自动化/excel/data/数据准备csv.md: -------------------------------------------------------------------------------- 1 | 这是一段嵌入的 csv 表格文件、让我想一想 -------------------------------------------------------------------------------- /docs/办公自动化/excel/images/第三方库对比.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PY-GZKY/PythonAutomatedOfficeGuide/014509edfeff065588a1391c5a3e116976a40531/docs/办公自动化/excel/images/第三方库对比.png -------------------------------------------------------------------------------- /docs/办公自动化/excel/pandas.md: -------------------------------------------------------------------------------- 1 | ## Pandas 快速开始 2 | 3 | 安装 `pandas` 以及相关依赖 4 | 5 | > 请确认已经安装了xlrd、 xlwt、 openpyxl 这三个包 6 | 7 | ```shell 8 | pip install pandas==1.3.0 9 | pip install xlrd # xlrd 版本不得高于2.0.0 10 | pip install xlwt 11 | pip install openpyxl 12 | ``` 13 | 14 | ```python 15 | import pandas as pd 16 | 17 | pd.__version__ # 1.3.0 18 | ``` 19 | 20 | ## 基本的文件读取和写入 21 | 22 | ### 数据读取 23 | 24 | pandas支持多种文件格式的读取、这里主要介绍读取csv, excel, txt 文件格式、其余格式请自行扩展。 25 | 26 | ```python 27 | import pandas as pd 28 | 29 | df_csv = pd.read_csv('../data/xhs_chengdu.csv') 30 | print(df_csv.head(10)) 31 | # print(df_csv.head(10).to_markdown()) 32 | ``` 33 | 34 | ```python 35 | import pandas as pd 36 | 37 | df_ = pd.read_excel('../data/xhs_chengdu.xlsx') 38 | print(df_.head(10).to_markdown()) 39 | ``` 40 | 41 | 这里有一些常用的公共参数,header=None表示第一行不作为列名,index_col表示把某一列或几列作为索引,索引的内容将会在第三章进行详述,usecols表示读取列的集合,默认读取所有的列,parse_dates表示需要转化为时间的列,关于时间序列的有关内容将在第十章讲解,nrows表示读取的数据行数。上面这些参数在上述的三个函数里都可以使用。 42 | 比如: 43 | 44 | ```python 45 | import pandas as pd 46 | 47 | df_ = pd.read_excel('../data/xhs_chengdu.xlsx', header=None) # 忽略表头 48 | # df_ = pd.read_excel('../data/xhs_chengdu.xlsx',usecols=['city', 'title']) # 只读取 col1,col2 列 49 | # df_ = pd.read_excel('../data/xhs_chengdu.xlsx',parse_dates=['pub_time']) # 需要转化为时间的列(pub_time) 50 | # df_ = pd.read_excel('../data/xhs_chengdu.xlsx', nrows=2) # 只读取 2 行 51 | print(df_.head(10).to_markdown()) # 输出前 10 行并转化为 markdown 语法 52 | ``` 53 | 54 | 如果想要把表格快速转换为markdown和latex语言,可以使用to_markdown和to_latex函数,此处需要安装tabulate包。 55 | 56 | ```shell 57 | pip install tabulate 58 | ``` 59 | 60 | 这里笔者比较提前安装完毕了。 61 | 62 | | | city | collects | comments | pub_time | title | update_time | username | 63 | |---:|:-------|-----------:|-----------:|:-----------------|:-----------------------------------------|:--------------------|:------------------| 64 | | 0 | 成都 | 998 | 10 | 2020-06-11 14:26 | 成都重庆旅游攻略|超详细,假期出游必备 | 2021-08-26 16:47:27 | Starman | 65 | | 1 | 成都 | 160 | 57 | 2021-08-11 20:34 | 夏日出行|超详细的成都融创乐园攻略! | 2021-08-26 16:47:31 | 荔枝太郎 | 66 | | 2 | 成都 | 2127 | 137 | 2020-06-11 19:49 | 成都夜市最强攻略合集,总有一个能打动你 | 2021-08-26 16:47:36 | 金克丝 | 67 | | 3 | 成都 | 2403 | 152 | 2019-08-16 21:49 | 五一小长假怎么花1500元玩转重庆和成都👇 | 2021-08-26 16:47:40 | 旅拍摄影师小周周 | 68 | | 4 | 成都 | 568 | 53 | 2021-07-27 17:21 | 重庆成都5天4夜一站式旅游攻略🔥人均1000 | 2021-08-26 16:47:44 | 不眠的小嗷娇 | 69 | | 5 | 成都 | 1302 | 20 | 2021-04-26 12:31 | 2021年成都私藏攻略㊙️五一小长假旅游必看 | 2021-08-26 16:47:48 | 150斤少女 | 70 | | 6 | 成都 | 700 | 7 | 2021-04-25 08:40 | 成都三天两晚保姆级攻略🔵人均1000💰快收藏 | 2021-08-26 16:47:52 | 雪琪小仙女 | 71 | | 7 | 成都 | 564 | 36 | 2021-03-25 23:04 | 成都必去的12个景点门票攻略~ | 2021-08-26 16:47:57 | 雪琪小仙女 | 72 | | 8 | 成都 | 3190 | 122 | 2021-04-21 16:59 | 成都旅游攻略|网红美食避坑|本地人推荐‼️ | 2021-08-26 16:48:01 | 破产兄弟BrokeBros | 73 | | 9 | 成都 | 963 | 39 | 2021-06-11 11:35 | 成都旅游‼️本地人整理三天两夜吃喝全攻略 | 2021-08-26 16:48:05 | 150斤少女 | 74 | 75 | > 如表格格式有错乱、笔者推荐使用 jupyter notebook 76 | 77 | ```python 78 | import pandas as pd 79 | 80 | df_csv = pd.read_table('../data/xhs_chengdu.txt') 81 | print(df_csv.head(5)) 82 | ``` 83 | 84 | 在读取txt文件时,经常遇到分隔符非空格的情况,read_table 有一个分割参数sep, 它使得用户可以自定义分割符号,进行txt数据的读取。例如,下面的读取的表以 abc 为分割: 85 | 86 | 这里有一份 txt 文件格式的数据、它以 abc 为分割符: 87 | 88 | ```text 89 | col1 abc col2 90 | TS abc This is an apple. 91 | GQ abc My name is Bob. 92 | WT abc Well done! 93 | PT abc May I help you? 94 | ``` 95 | 96 | ```python 97 | import pandas as pd 98 | 99 | df_ = pd.read_table('../data/my_table_special_sep.txt') 100 | ``` 101 | 102 | | | col1 abc col2 | 103 | |---:|:-------------------------| 104 | | 0 | TS abc This is an apple. | 105 | | 1 | GQ abc My name is Bob. | 106 | | 2 | WT abc Well done! | 107 | | 3 | PT abc May I help you? | 108 | 109 | 上面的结果显然不是理想的,这时可以使用sep,同时需要指定引擎为python: 110 | 111 | ```python 112 | import pandas as pd 113 | 114 | df_ = pd.read_table('../data/my_table_special_sep.txt', sep=' abc ', engine='python') 115 | ``` 116 | 117 | 得到: 118 | 119 | | | col1 | col2 | 120 | |---:|:-------|:------------------| 121 | | 0 | TS | This is an apple. | 122 | | 1 | GQ | My name is Bob. | 123 | | 2 | WT | Well done! | 124 | | 3 | PT | May I help you? | 125 | 126 | > 【WARNING】sep是正则参数 在使用read_table的时候需要注意,参数sep中使用的是正则表达式,因此需要对|进行转义变成\|,否则无法读取到正确的结果。有关正则表达式的基本内容可以参考第八章或者其他相关资料。 127 | 128 | ### 数据写入 129 | 130 | 一般在数据写入中,最常用的操作是把index设置为False,特别当索引没有特殊意义的时候,这样的行为能把索引在保存的时候去除。 131 | 132 | [](data/数据准备csv.md ':include') 133 | 134 | 将读取到的 csv 文件数据写入到 xlsx 文件(反之亦然) 135 | 136 | ```python 137 | import pandas as pd 138 | 139 | df_ = pd.read_excel('../data/xhs_chengdu.xlsx') # 忽略表头 140 | df_.to_excel('../data/my_csv_saved.xlsx', index=False) 141 | ``` 142 | 143 | ## 常用方法和基本数据结构 144 | 145 | - `pd.Series` 方法: 一维的行数据结构 146 | - `pd.DataFrame` 方法: 二维的表格结构(常用于读写转换) 147 | 148 | 构建 Series 149 | 150 | ```python 151 | import pandas as pd 152 | 153 | s = pd.Series(data=[100, 'a', {'dic1': 5}], 154 | index=pd.Index(['id1', 20, 'third'], name='my_idx'), 155 | dtype='object', 156 | name='my_name') 157 | 158 | ``` 159 | 160 | object代表了一种混合类型,正如上面的例子中存储了整数、字符串以及Python的字典数据结构。此外,目前pandas把纯字符串序列也默认认为是一种object类型的序列 161 | 162 | DataFrame在Series的基础上增加了列索引,一个数据框可以由二维的data与行列索引来构造 163 | 164 | ```python 165 | import pandas as pd 166 | 167 | data = [[1, 'a', 1.2], [2, 'b', 2.2], [3, 'c', 3.2]] 168 | df = pd.DataFrame(data=data, 169 | index=['row_%d' % i for i in range(3)], 170 | columns=['col_0', 'col_1', 'col_2']) 171 | ``` 172 | 173 | ## 缺失值的处理 174 | 175 | ... 176 | 177 | ## pandas 进阶使用 178 | 179 | ... 180 | 181 | ## 文件转储(与数据库交互) 182 | 183 | #### sql 转文件 184 | 185 | `pandas` 可以读取数据库中的表并转换成表格文件 (诸如 `csv`、`xlsx`、`json` 格式的文件) 186 | 187 | 在 `pandas` 中有一个 `read_sql` 可以快速的对数据库进行查询操作。 需要注意的是目前 `pandas` 所支持的数据库引擎并不是很多(比如支持 `mysql` 而不支持 `mongodb`) 188 | 189 | 这可能会是一个小小的痛点,不过没关系,我们可以通过其他的库进行读取操作后将数据进行写入操作,这个后面会提到。 190 | 191 | `read_sql` 方法的参数如下: 192 | 193 | ```python 194 | read_sql(sql, con, index_col=None, coerce_float=True, params=None, parse_dates=None, columns=None, chunksize=None) 195 | ``` 196 | 197 | 可以看到前两个是比较重要的参数 198 | 199 | - `sql` 为可执行的原生sql语句 200 | - `con` 是一个数据库连接`实例对象` (或者说是连接句柄) 201 | 202 | 对于 `mysql`,这里习惯采用常用的第三方库 `pymysql` 来进行演示: 203 | 204 | ```python 205 | import pandas as pd 206 | import pymysql 207 | 208 | conn = pymysql.connect( 209 | host='localhost', 210 | user='root', 211 | passwd='123456', 212 | db='test_', 213 | port=3306, 214 | charset='utf8mb4' 215 | ) 216 | df = pd.read_sql('select * from test_', conn) 217 | df.head() 218 | ``` 219 | 220 | 可以看出 `read_sql` 方法返回的就是一个 `dataframe` 对象,可以直接转换为 `csv、excel`等表格文件 221 | 222 | ```python 223 | df.to_csv() 224 | ``` 225 | 226 | #### 文件转 sql 227 | 228 | 相对应的 pandas 实现了 to_sql 方法来将文件中的数据转换为数据表 229 | 230 | ```python 231 | to_sql(name, con, flavor=None, schema=None, if_exists='fail', index=True, index_label=None, chunksize=None, dtype=None) 232 | ``` 233 | 234 | 使用的方法是: 235 | 236 | ```python 237 | df.to_sql(name='test_', con='mysql+pymysql://root:123456@localhost:3306/test_?charset=utf8mb4', if_exists='replace', 238 | index=False) 239 | ``` 240 | 241 | - name: 参数为存储的表名 242 | - con: 连接数据库的字符串句柄 243 | - if_exists: 用于判断是否有重复表名 244 | - fail: 如果有重复表名,就不保存,略过 245 | - replace: 用作替换 246 | - append: 就在该表中继续插入数据 -------------------------------------------------------------------------------- /docs/办公自动化/excel/polars.md: -------------------------------------------------------------------------------- 1 | ## Polars 快速开始 2 | 3 | 从一定程度上说、`pandas` 可以完美的应用于我们的日常办公和数据处理。(Pandas 发布于 2008 年、至今14年了神奇吧、使用 Python、Cython 和 C 编写的) 4 | 5 | 比较遗憾的是 `pandas` 对于大数据量的读写操作竟有些吃力、性能和处理速度往往不如人意、所以北极熊来了。 6 | 7 | `polars` 是通过 `Rust` 编写的一个库,`polars` 的内存模型是基于 `Apache Arrow`。 8 | 9 | `polars` 在很大程度上借鉴了 `pandas` 的语法风格、所以熟悉 `pandas` 的用户可直接上手。 10 | 11 | 12 | ## 安装 13 | 14 | ```shell 15 | pip install polars 16 | ``` 17 | 18 | ```python 19 | import polars as pl 20 | pl.__version__ # 0.13.4 21 | ``` 22 | 23 | ## 基本数据结构 24 | 25 | ### Series && DataFrame 26 | ```python 27 | import polars as pl 28 | import numpy as np 29 | 30 | np.random.seed(12) 31 | 32 | df = pl.DataFrame( 33 | { 34 | "nrs": [1, 2, 3, None, 5], 35 | "names": ["foo", "ham", "spam", "egg", None], 36 | "random": np.random.rand(5), 37 | "groups": ["A", "A", "B", "C", "B"], 38 | } 39 | ) 40 | print(df) 41 | ``` 42 | ### from_dict 43 | ### from_arrow 44 | ### from_pandas 45 | 46 | 47 | ```python 48 | ``` 49 | 50 | 51 | ## 基本的读取和写入 52 | 53 | ### 数据读取 54 | 55 | ```python 56 | ``` 57 | 58 | ```python 59 | ``` 60 | 61 | 62 | 如果想要把表格快速转换为markdown和latex语言,可以使用to_markdown和to_latex函数,此处需要安装tabulate包。 63 | 64 | ```shell 65 | pip install tabulate 66 | ``` 67 | 68 | 这里笔者比较提前安装完毕了。 69 | 70 | | | city | collects | comments | pub_time | title | update_time | username | 71 | |---:|:-------|-----------:|-----------:|:-----------------|:-----------------------------------------|:--------------------|:------------------| 72 | | 0 | 成都 | 998 | 10 | 2020-06-11 14:26 | 成都重庆旅游攻略|超详细,假期出游必备 | 2021-08-26 16:47:27 | Starman | 73 | | 1 | 成都 | 160 | 57 | 2021-08-11 20:34 | 夏日出行|超详细的成都融创乐园攻略! | 2021-08-26 16:47:31 | 荔枝太郎 | 74 | | 2 | 成都 | 2127 | 137 | 2020-06-11 19:49 | 成都夜市最强攻略合集,总有一个能打动你 | 2021-08-26 16:47:36 | 金克丝 | 75 | | 3 | 成都 | 2403 | 152 | 2019-08-16 21:49 | 五一小长假怎么花1500元玩转重庆和成都👇 | 2021-08-26 16:47:40 | 旅拍摄影师小周周 | 76 | | 4 | 成都 | 568 | 53 | 2021-07-27 17:21 | 重庆成都5天4夜一站式旅游攻略🔥人均1000 | 2021-08-26 16:47:44 | 不眠的小嗷娇 | 77 | | 5 | 成都 | 1302 | 20 | 2021-04-26 12:31 | 2021年成都私藏攻略㊙️五一小长假旅游必看 | 2021-08-26 16:47:48 | 150斤少女 | 78 | | 6 | 成都 | 700 | 7 | 2021-04-25 08:40 | 成都三天两晚保姆级攻略🔵人均1000💰快收藏 | 2021-08-26 16:47:52 | 雪琪小仙女 | 79 | | 7 | 成都 | 564 | 36 | 2021-03-25 23:04 | 成都必去的12个景点门票攻略~ | 2021-08-26 16:47:57 | 雪琪小仙女 | 80 | | 8 | 成都 | 3190 | 122 | 2021-04-21 16:59 | 成都旅游攻略|网红美食避坑|本地人推荐‼️ | 2021-08-26 16:48:01 | 破产兄弟BrokeBros | 81 | | 9 | 成都 | 963 | 39 | 2021-06-11 11:35 | 成都旅游‼️本地人整理三天两夜吃喝全攻略 | 2021-08-26 16:48:05 | 150斤少女 | 82 | 83 | > 如表格格式有错乱、笔者推荐使用 jupyter notebook 84 | 85 | 86 | ### 数据写入 87 | 88 | ```python 89 | ``` 90 | 91 | -------------------------------------------------------------------------------- /docs/办公自动化/excel/readme.md: -------------------------------------------------------------------------------- 1 | excel是自动化办公领域的主战场,在日常工作中 `excel` 表格操作频繁,对我们的重要程度不言而喻 2 | 3 | 那么 `Python` 与 `excel` 之间的交互就成了我们关注的话题 4 | 5 | ### 常见的第三方库 6 | 7 | [![xlrd](https://img.shields.io/badge/xlrd-latest-brightgreen)](http://xlrd.readthedocs.io/en/latest/) 8 | [![xlwt](https://img.shields.io/badge/xlwt-latest-yellow)](https://xlwt.readthedocs.io/en/latest/) 9 | [![xlutils](https://img.shields.io/badge/xlutils-latest-green)](http://xlutils.readthedocs.io/en/latest/) 10 | [![xlwings](https://img.shields.io/badge/xlwings-latest-orange)](http://docs.xlwings.org/en/stable/index.html) 11 | 12 | [![openpyxl](https://img.shields.io/badge/openpyxl-latest-red)](https://openpyxl.readthedocs.io/en/stable/) 13 | [![xlsxwriter](https://img.shields.io/badge/xlsxwriter-latest-blue)](https://xlsxwriter.readthedocs.io/) 14 | [![pandas](https://img.shields.io/badge/pandas-1.3.0-yellow)](http://pandas.pydata.org/) 15 | 16 | 17 | 18 | ![图片来源于网络](./images/第三方库对比.png) 19 | 20 | 虽然每个库都有其自身的优缺点,都有其适合的应用场景,然而就通用程度而言, 21 | 笔者推荐使用 `xlwings` 和 `openpyxl` 库来进行 `excel` 的读写操作。 22 | 23 | ### 安装 24 | ```shell 25 | pip install xlrd 26 | pip install xlwt 27 | pip install xlutils 28 | pip install xlwings 29 | pip install XlsxWriter 30 | pip install openpyxl 31 | pip install pandas 32 | ``` 33 | 34 | > 该文档以 `openpyxl` 和 `pandas` 为例做读写文件操作 35 | -------------------------------------------------------------------------------- /docs/办公自动化/excel/一些案例.md: -------------------------------------------------------------------------------- 1 | ### 创建表格 2 | ```python 3 | # -*- coding: utf-8 -*- 4 | import os 5 | from openpyxl import Workbook 6 | 7 | def createExcel(file_path): 8 | wb = Workbook() 9 | ws = wb.active 10 | ws.title = '搜索指数概览' 11 | ws.append(['关键词', '整体日均值', '移动日均值', '整体同比', '整体环比', '移动同比', '移动环比']) 12 | ws.append(['excel', 27782, 18181, -0.11, -2, 0.21, 0.02]) 13 | ws.append(['python', 24267, 8204, 0.27, 0.06, 0.56, 0.01]) 14 | ws.append(['文案', 2411, 1690, 0.56, 0.33, 0.91, 0.46]) 15 | ws.append(['okr', 1928, 880, 0.38, 0.15, 0.29, 0.09]) 16 | ws.append(['kpi', 4212, 2784, 0.21, -0.19, 0.36, -0.22]) 17 | wb.save(file_path) 18 | 19 | 20 | if __name__ == '__main__': 21 | if not os.path.exists('out'): 22 | os.mkdir('out') 23 | createExcel('./指数.xlsx') 24 | 25 | ``` 26 | 27 | ### 插入数据 28 | ```python 29 | # -*- coding: utf-8 -*- 30 | """ 31 | @Time : 2021/4/21 13:17 32 | @Auth : wutong 33 | @File :快速开始.py 34 | @IDE :PyCharm 35 | """ 36 | import datetime 37 | from openpyxl import Workbook 38 | from openpyxl.utils import get_column_letter 39 | # 初始化新的excel表格 40 | wb = Workbook() 41 | 42 | # 创建一个子表 43 | ws = wb.create_sheet("first_sheet", 0) # insert at first position 44 | 45 | # ws['A1'] = datetime.datetime(2010, 7, 21) 46 | # ws["A2"] = "=SUM(15, 1)" 47 | 48 | # ws.merge_cells('A1:A2') 49 | # ws.unmerge_cells('A1:A2') 50 | 51 | ws.insert_rows(7) 52 | 53 | for row in range(1, 40): 54 | ws.append(range(600)) 55 | 56 | # for row in range(10, 20): 57 | # for col in range(27, 54): 58 | # ws.cell(column=col, row=row, value="{0}".format(get_column_letter(col))) 59 | 60 | 61 | # 获取最大行 62 | row_max = ws.max_row 63 | # 获取最大列 64 | con_max = ws.max_column 65 | # 把上面写入内容打印在控制台 66 | for j in ws.rows: # we.rows 获取每一行数据 67 | for n in j: 68 | print(n.value, end="\t") # n.value 获取单元格的值 69 | print() 70 | 71 | # 保存 72 | wb.save('./balances.xlsx') 73 | ``` 74 | ### 添加样式 75 | ```python 76 | # 现在我们对整个表进行设置 77 | 78 | # 读取 79 | from openpyxl import load_workbook 80 | from openpyxl.styles import Alignment, Border, Font, NamedStyle, PatternFill, Side 81 | 82 | wb = load_workbook('./指数.xlsx') 83 | ws = wb['搜索指数概览'] 84 | 85 | # 读取数据表格范围 86 | rows = ws.max_row 87 | cols = ws.max_column 88 | 89 | # 字体 90 | font1 = Font(name='微软雅黑', size=11, b=True) 91 | font2 = Font(name='微软雅黑', size=11) 92 | 93 | # 边框 94 | line_t = Side(style='thin', color='000000') # 细边框 95 | line_m = Side(style='medium', color='000000') # 粗边框 96 | border1 = Border(top=line_m, bottom=line_t, left=line_t, right=line_t) 97 | # 与标题相邻的边设置与标题一样 98 | border2 = Border(top=line_t, bottom=line_t, left=line_t, right=line_t) 99 | 100 | # 填充 101 | fill = PatternFill('solid', fgColor='CFCFCF') 102 | 103 | # 对齐 104 | alignment = Alignment(horizontal='center', vertical='center') 105 | 106 | # 将样式打包命名 107 | sty1 = NamedStyle(name='sty1', font=font1, fill=fill,border=border1, alignment=alignment) 108 | sty2 = NamedStyle(name='sty2', font=font2, border=border2, alignment=alignment) 109 | 110 | for r in range(1, rows+1): 111 | for c in range(1, cols+1): 112 | if r == 1: 113 | ws.cell(r, c).style = sty1 114 | else: 115 | ws.cell(r, c).style = sty2 116 | 117 | wb.save('./指数_style.xlsx') 118 | ``` 119 | 120 | ```python 121 | # -*- coding: utf-8 -*- 122 | from openpyxl import load_workbook 123 | from openpyxl.styles import Alignment, Border, Font, PatternFill, Side 124 | 125 | wb = load_workbook('./指数.xlsx') # 读取修改后的文件 126 | ws = wb['搜索指数概览'] 127 | 128 | a1 = ws['A1'] 129 | ws['A2'] = 'word' 130 | 131 | # 设置字体 132 | ft = Font(name='微软雅黑', color='000000', size=15, b=True) 133 | """ 134 | name:字体名称 135 | color:颜色通常是RGB或aRGB十六进制值 136 | b(bold):加粗(bool) 137 | i(italic):倾斜(bool) 138 | shadow:阴影(bool) 139 | underline:下划线(‘doubleAccounting’, ‘single’, ‘double’, ‘singleAccounting’) 140 | charset:字符集(int) 141 | strike:删除线(bool) 142 | """ 143 | 144 | a1.font = ft 145 | 146 | # 设置文本对齐 147 | 148 | ali = Alignment(horizontal='center', vertical='center') 149 | """ 150 | horizontal:水平对齐('centerContinuous', 'general', 'distributed', 151 | 'left', 'fill', 'center', 'justify', 'right') 152 | vertical:垂直对齐('distributed', 'top', 'center', 'justify', 'bottom') 153 | 154 | """ 155 | a1.alignment = ali 156 | 157 | # 设置图案填充 158 | 159 | fill = PatternFill('solid', fgColor='FFA500') 160 | # 颜色一般使用十六进制RGB 161 | # 'solid'是图案填充类型,详细可查阅文档 162 | 163 | a1.fill = fill 164 | 165 | # 设置边框 166 | bian = Side(style='medium', color='000000') # 设置边框样式 167 | """ 168 | style:边框线的风格{'dotted','slantDashDot','dashDot','hair','mediumDashDot', 169 | 'dashed','mediumDashed','thick','dashDotDot','medium', 170 | 'double','thin','mediumDashDotDot'} 171 | """ 172 | 173 | border = Border(top=bian, bottom=bian, left=bian, right=bian) 174 | """ 175 | top(上),bottom(下),left(左),right(右):必须是 Side类型 176 | diagonal: 斜线 side类型 177 | diagonalDownd: 右斜线 bool 178 | diagonalDown: 左斜线 bool 179 | """ 180 | 181 | # a1.border = border 182 | for item in ws['A1:G1'][0]: # 去元组中的每一个cell更改样式 183 | item.border = border 184 | item.font = ft 185 | item.alignment = ali 186 | 187 | wb.save('./指数_单元格_style.xlsx') # 保存更改 188 | 189 | ``` 190 | ### `Word`、`Excel` 191 | 192 | 193 | 194 | ### 数据库、`Excel` -------------------------------------------------------------------------------- /docs/办公自动化/excel/常用功能.md: -------------------------------------------------------------------------------- 1 | 2 | 众所周知 3 | 4 | - `workbook` 工作簿 5 | - `worksheet` 工作表,一个`workbook`可以有多子工作表表,可以自定义命名 6 | - `cell` 单元格,存储数据的最小单位 7 | 8 | ## 初始化 `Excel` 9 | 10 | ```python 11 | from openpyxl import Workbook 12 | wb = Workbook() 13 | ws = wb.active 14 | ``` 15 | 16 | - `Workbook()` 初始化一个 `Workbook` 对象,默认将创建一个 `excel` 表格 17 | - `wb.active` 激活状态 18 | 19 | 20 | ## 写入操作 21 | 22 | ### 创建`sheet`表 23 | ```python 24 | # -*- coding: utf-8 -*- 25 | from openpyxl import Workbook 26 | 27 | wb = Workbook() 28 | ws = wb.create_sheet("first_sheet", 0) # 创建子表 29 | ``` 30 | 31 | ### 写入单元格 32 | 在 `first_sheet` 中的单元格写入数据 33 | ```python 34 | from openpyxl import Workbook 35 | import datetime 36 | wb = Workbook() 37 | 38 | ws = wb.create_sheet("first_sheet", 0) 39 | ws['A1'] = datetime.datetime(2010, 7, 21) 40 | ws["A2"] = "你好桐哥" 41 | ws["A3"] = "=SUM(15, 1)" # 支持excel运算表达式 42 | ``` 43 | 44 | 45 | 46 | ```python 47 | from openpyxl import load_workbook 48 | wb = load_workbook('./5a.xlsx') 49 | for sheet_name in wb.sheetnames: # 返回子表列表 50 | print(sheet_name) 51 | ``` 52 | 53 | ### 获取最大行数和列数 54 | ```python 55 | print('最大列数为:', ws.max_column) 56 | print('最大行数为:', ws.max_row) 57 | ``` 58 | 59 | ### 插入和删除行 60 | ```python 61 | # 插入行和列 62 | ws.insert_rows(1) # 在第一行插入一行 63 | ws.insert_cols(2, 4) # 从第二列开始插入四列 64 | 65 | # 删除行和列 66 | ws.delete_cols(6, 3) # 从第六列(F列)开始,删除3列即(F:H) 67 | ws.delete_rows(3) # 删除第三行 68 | ``` 69 | 70 | 71 | ## 插入图片 72 | ```python 73 | from openpyxl import load_workbook 74 | from openpyxl.drawing.image import Image 75 | 76 | workbook = load_workbook(filename="./okok.xlsx") 77 | sheet = workbook.active 78 | 79 | logo = Image("./ico.jpg") 80 | logo.height = 200 81 | logo.width = 200 82 | 83 | sheet.add_image(logo,"D2") 84 | 85 | workbook.save(filename="./okok.xlsx") 86 | 87 | ``` 88 | 89 | ### 合并单元格 90 | ```python 91 | # 合并单元格, 往左上角写入数据即可 92 | sheet.merge_cells('B1:G1') # 合并一行中的几个单元格 93 | sheet.merge_cells('A1:C3') # 合并一个矩形区域中的单元格 94 | 95 | sheet.unmerge_cells('A1:C3') 96 | ``` 97 | 98 | ## 样式 99 | 更多时候,我们需要为excel表格添加各式各样的风格 100 | ```python 101 | # 下面的代码指定了等线24号,加粗斜体,字体颜色红色。直接使用cell的font属性,将Font对象赋值给它。 102 | 103 | bold_itatic_24_font = Font(name='等线', size=24, italic=True, color=colors.RED, bold=True) 104 | 105 | sheet['A1'].font = bold_itatic_24_font 106 | 107 | # 对齐方式 108 | # 也是直接使用cell的属性aligment,这里指定垂直居中和水平居中。除了center,还可以使用right、left等等参数。 109 | 110 | # 设置B1中的数据垂直居中和水平居中 111 | sheet['B1'].alignment = Alignment(horizontal='center', vertical='center') 112 | 113 | # 第2行行高 114 | sheet.row_dimensions[2].height = 40 115 | # C列列宽 116 | sheet.column_dimensions['C'].width = 30 117 | ``` 118 | 119 | 120 | ## 读取 Excel 121 | 122 | ```python 123 | from copy import copy 124 | 125 | from openpyxl import load_workbook 126 | from openpyxl.styles import Alignment 127 | 128 | from excel自动化 import 创建excel as create_data_excel 129 | 130 | def read_xlsx_basic(file_path): 131 | """ 132 | 读取Excel的数据并打印出来 133 | """ 134 | wb = load_workbook(file_path) 135 | st = wb.active 136 | end_row = st.max_row + 1 137 | end_column = st.max_column + 1 138 | print(st.title, '有', end_row, '行', end_column, '列') 139 | for row in range(1, end_row): 140 | for col in range(1, end_column): 141 | print('{:10}'.format(st.cell(row=row, column=col).value), end='') 142 | print() 143 | 144 | 145 | def change_title_line_format(file_path): 146 | wb = load_workbook(file_path) 147 | st = wb.active 148 | 149 | # 调整宽度 150 | for col in range(ord('A'), ord('G') + 1): 151 | st.column_dimensions[chr(col)].width = 15 152 | 153 | # 调整第一行的单元格 154 | for col in range(1, st.max_column + 1): 155 | cell = st.cell(row=1, column=col) 156 | font = copy(cell.font) # 调整字体 157 | font.size = 13 158 | font.bold = True 159 | cell.font = font 160 | 161 | if col > 1: 162 | cell.alignment = Alignment(horizontal="center", vertical="center") # 居中显示 163 | 164 | # 调整数据单元格 165 | for row in range(2, st.max_row + 1): 166 | for col in range(2, st.max_column + 1): 167 | cell = st.cell(row=row, column=col) 168 | cell.alignment = Alignment(horizontal="center", vertical="center") # 居中显示 169 | 170 | # 调整单元格的格式 - 显示百分比 171 | for row in range(2, st.max_row + 1): 172 | for col in range(4, st.max_column + 1): 173 | cell = st.cell(row=row, column=col) 174 | cell.number_format = '0%' 175 | 176 | st.cell(row=2, column=7).value = 0.42 # 修改数值 177 | wb.save(file_path) 178 | 179 | 180 | if __name__ == '__main__': 181 | excel_path = './out/指数.xlsx' 182 | create_data_excel.create_excel_demo(excel_path) 183 | ws1 = load_workbook(excel_path).active 184 | if ws1 is None: 185 | print('表格不存在') 186 | else: 187 | print('读取基本数据') 188 | read_xlsx_basic(excel_path) 189 | change_title_line_format(excel_path) 190 | ``` 191 | 192 | 193 | ## 举个例子 194 | ```python 195 | import datetime 196 | from random import choice 197 | from time import time 198 | from openpyxl import load_workbook 199 | from openpyxl.utils import get_column_letter 200 | 201 | # 设置文件 mingc 202 | addr = "openpyxl.xlsx" 203 | # 打开文件 204 | wb = load_workbook(addr) 205 | # 创建一张新表 206 | ws = wb.create_sheet() 207 | # 第一行输入 208 | ws.append(['TIME', 'TITLE', 'A-Z']) 209 | 210 | # 输入内容(500行数据) 211 | for i in range(500): 212 | TIME = datetime.datetime.now().strftime("%H:%M:%S") 213 | TITLE = str(time()) 214 | A_Z = get_column_letter(choice(range(1, 50))) 215 | ws.append([TIME, TITLE, A_Z]) 216 | 217 | # 获取最大行 218 | row_max = ws.max_row 219 | # 获取最大列 220 | con_max = ws.max_column 221 | # 把上面写入内容打印在控制台 222 | for j in ws.rows: # we.rows 获取每一行数据 223 | for n in j: 224 | print(n.value, end="\t") # n.value 获取单元格的值 225 | print() 226 | # 保存,save(必须要写文件名(绝对地址)默认 py 同级目录下,只支持 xlsx 格式) 227 | wb.save(addr) 228 | ``` -------------------------------------------------------------------------------- /docs/办公自动化/markdown/markdown_.md: -------------------------------------------------------------------------------- 1 | ## markdown转换为其他格式的文件 2 | 3 | ### markdown2html 4 | 5 | ```python 6 | import codecs 7 | import os 8 | 9 | try: 10 | from markdown import markdown 11 | except ModuleNotFoundError as e: 12 | os.system("pip install markdown") 13 | os.system("pip install python-markdown-math") 14 | os.system("pip install markdown_checklist") 15 | from markdown import markdown 16 | 17 | from src.constants import * 18 | from src.source import imgkit 19 | from src.source import pdfkit 20 | from src.templates.html_template import html_ 21 | from src.utils.utils import md_extensions_, md_extensions_configs_ 22 | 23 | 24 | class MARKDOWN: 25 | def __init__(self, 26 | wkhtmltopdf_path: str = None, 27 | wkhtmltoimage_path: str = None, 28 | encoding: str = 'utf-8' 29 | ): 30 | self.encoding = encoding 31 | self.html_ = html_ 32 | self.wkhtmltopdf_path = default_wkhtmltopdf_path if wkhtmltopdf_path is None else wkhtmltopdf_path 33 | self.wkhtmltoimage_path = default_wkhtmltoimage_path if wkhtmltoimage_path is None else wkhtmltoimage_path 34 | self.extensions = md_extensions_() 35 | self.extension_configs = md_extensions_configs_() 36 | 37 | def markdown2html(self, input_path: str, output_path: str, is_center: bool = True, is_save: bool = True): 38 | """ 39 | """ 40 | try: 41 | with codecs.open(input_path, "r", encoding="utf-8") as md_: 42 | md_text_ = md_.read() 43 | except Exception as e: 44 | print("", e) 45 | 46 | title = '.'.join(os.path.basename(input_path).split('.')[:-1]) 47 | html_text_ = markdown(md_text_, 48 | output_format='html', 49 | extensions=self.extensions, 50 | extension_configs=self.extension_configs 51 | ) 52 | 53 | class_ = ' for="html-export"' if is_center else "" 54 | html_text_ = self.html_.format(title_=title, static_dir=static_dir, div_=html_text_, class_=class_) 55 | 56 | if is_save: 57 | try: 58 | with codecs.open(output_path, 'w', encoding=self.encoding, errors="xmlcharrefreplace") as file_html_: 59 | file_html_.write(html_text_) 60 | return output_path 61 | except Exception: 62 | return False 63 | else: 64 | return html_text_ 65 | 66 | 67 | if __name__ == '__main__': 68 | M = MARKDOWN() 69 | M.markdown2html(input_path=f'./_.md') 70 | ``` -------------------------------------------------------------------------------- /docs/办公自动化/pdf/readme.md: -------------------------------------------------------------------------------- 1 | 2 | 除了 `office` 办公三件套,这里将 `PDF` 作为一种辅助。 3 | 4 | > 这里使用的扩展库是 `pdfminer` 以及 `pdfplumber` 5 | 6 | ```python 7 | import pdfminer 8 | from pdfminer.pdfparser import PDFParser 9 | from pdfminer.pdfdocument import PDFDocument 10 | from pdfminer.pdfpage import PDFPage 11 | from pdfminer.pdfpage import PDFTextExtractionNotAllowed 12 | from pdfminer.pdfinterp import PDFResourceManager 13 | from pdfminer.pdfinterp import PDFPageInterpreter 14 | from pdfminer.pdfdevice import PDFDevice 15 | from pdfminer.layout import * 16 | from pdfminer.converter import PDFPageAggregator 17 | 18 | # 提供初始密码 19 | password = '' 20 | # 没有密码可以初始密码 21 | # document.initialize() 22 | 23 | #打开pdf文件 24 | fp = open('./Netease Q2 2019 Earnings Release-Final.pdf','rb') 25 | 26 | #从文件句柄创建一个pdf解析对象 27 | parser = PDFParser(fp) 28 | 29 | #创建pdf文档对象,存储文档结构 30 | document = PDFDocument(parser, password) 31 | 32 | #创建一个pdf资源管理对象,存储共享资源 33 | rsrcmgr = PDFResourceManager() 34 | 35 | laparams = LAParams() 36 | 37 | #创建一个device对象 38 | device = PDFPageAggregator(rsrcmgr, laparams=laparams) 39 | 40 | #创建一个解释对象 41 | interpreter = PDFPageInterpreter(rsrcmgr, device) 42 | 43 | #处理包含在文档中的每一页 44 | for page in PDFPage.create_pages(document): 45 | interpreter.process_page(page) 46 | layout = device.get_result() 47 | for x in layout: 48 | # 获取文本对象 49 | if isinstance(x, LTTextBox): 50 | print(x.get_text().strip()) 51 | # 获取图片对象 52 | if isinstance(x,LTImage): 53 | print('这里获取到一张图片') 54 | # 获取 figure 对象 55 | if isinstance(x,LTFigure): 56 | print('这里获取到一个 figure 对象') 57 | ``` 58 | 59 | 60 | ```python 61 | import pdfplumber 62 | import pandas as pd 63 | from openpyxl import Workbook 64 | # with pdfplumber.open("1.pdf") as pdf: 65 | # page = pdf.pages[0] # 第一页的信息 66 | # text = page.extract_text() 67 | # print(text) 68 | 69 | 70 | with pdfplumber.open("Netease Q2 2019 Earnings Release-Final.pdf") as pdf: 71 | page = pdf.pages[9] # 第一页的信息 72 | table = page.extract_table( 73 | table_settings= { 74 | "vertical_strategy":"text", 75 | "horizontal_strategy":"text" 76 | 77 | } 78 | ) 79 | workbook = Workbook() 80 | sheet = workbook.active 81 | for row in table: 82 | sheet.append(row) 83 | 84 | workbook.save(filename="./toPdf.xlsx") 85 | ``` -------------------------------------------------------------------------------- /docs/办公自动化/ppt/readme.md: -------------------------------------------------------------------------------- 1 | > excel 自动化章节 !!!!!!! -------------------------------------------------------------------------------- /docs/办公自动化/ppt/常用功能.md: -------------------------------------------------------------------------------- 1 | 2 | #### 快速创建一个 `ppt` 并尝试向它写入一点东西 3 | 4 | ```python 5 | # encoding:utf-8 6 | 7 | from pptx import Presentation 8 | 9 | prs = Presentation() 10 | title_slide_layout = prs.slide_layouts[0] 11 | slide = prs.slides.add_slide(title_slide_layout) 12 | title = slide.shapes.title 13 | subtitle = slide.placeholders[1] 14 | 15 | title.text = "Hello, World!" 16 | subtitle.text = "python-pptx was here!" 17 | 18 | prs.save('test.pptx') 19 | ``` 20 | 21 | #### 给点样式 22 | ```python 23 | # -*- coding: utf-8 -*- 24 | """ 25 | @Time : 2021/2/1 13:36 26 | @Auth : wutong 27 | @File :创建文字.py 28 | @IDE :PyCharm 29 | """ 30 | from pptx import Presentation 31 | from pptx.util import Inches, Pt 32 | 33 | prs = Presentation() 34 | slide = prs.slides.add_slide(prs.slide_layouts[1]) 35 | # 对ppt的修改 36 | 37 | body_shape = slide.shapes.placeholders # body_shape为本页ppt中所有shapes 38 | body_shape[0].text = '假如生活欺骗了你' # 在第一个文本框中文字框架内添加文字 39 | body_shape[1].text = ''' 40 | 假如生活欺骗了你, 41 | 不要悲伤,不要心急! 42 | ''' # 在第二个文本框中文字框架内添加文字 43 | 44 | new_paragraph = body_shape[1].text_frame.add_paragraph() # 在第二个shape中的文本框架中添加新段落 45 | new_paragraph.text = 'add_paragraph' # 新段落中文字 46 | new_paragraph.font.bold = True # 文字加粗 47 | new_paragraph.font.italic = True # 文字斜体 48 | new_paragraph.font.size = Pt(15) # 文字大小 49 | new_paragraph.font.underline = True # 文字下划线 50 | new_paragraph.level = 1 # 新段落的级别 51 | 52 | img_path = 'ico.jpg' # 文件路径 53 | left, top, width, height = Inches(1), Inches(4.5), Inches(2), Inches(2) # 预设位置及大小 54 | pic = slide.shapes.add_picture(img_path, left, top, width, height) # 在指定位置按预设值添加图片 55 | 56 | # left = top = width = height = Inches(5) # 预设位置及大小 57 | # textbox = slide.shapes.add_textbox(left, top, width, height) # left,top为相对位置,width,height为文本框大小 58 | # textbox.text = 'this is a new textbox' # 文本框中文字 59 | # new_para = textbox.text_frame.add_paragraph() # 在新文本框中添加段落 60 | # new_para.text = 'this is second para in textbox' # 段落文字 61 | 62 | prs.save('python-pptx.pptx') 63 | ``` 64 | 65 | -------------------------------------------------------------------------------- /docs/办公自动化/readme.md: -------------------------------------------------------------------------------- 1 | 2 | ### 关于现状 3 | 4 | 现在不少针对 `Python` 语言的一些诟病都是从其`运行速度`和`性能`开始的。 5 | 6 | 抛开动态语言的编译和运行速度不谈,在日常的应用中,除了这些`Python`公认的通病之外,你是否真正的把这门语言用到了极致。 7 | 8 | 很少吧,我们日常使用可能只是用到了 `Python`的冰山一角,因为这足以应付我们的日常工作,特别是对于`自动化办公`, 9 | 你可能不会深入去理解 `Python` 的一些`高阶用法`,`装饰器`、`闭包`、`元类`、`子类继承`、`多态`以及各种`设计模式`等等。 10 | 11 | 实话实说,现在不少的`初级工程师`也仅仅是在模块中实现一些简单明了的类示例,配合各种第三方库用起来也是无比的顺手,更重要的是保住了头发,这何乐而不为? 12 | 13 | 14 | ### 关于效率 15 | 16 | 如果你不再是一个新手,或者不想在外人看来像一个新手,你可能需要接触一些你日常代码之外的东西 (`Python` 之强大远不止于此) 17 | 18 | 如果你刚接触 `Python` 不久,那么你可能会使用诸如 `int`、 `str`、`list`、 `dict`、 `元组`、 `数组`等一些 `Python` 内置的基础`数据结构`,这很好。 19 | 20 | `Python` 的强大之处就在于简单的命令能让你看到一些不可思议的效果, 21 | 一个 `list` 类型再加上一个 `dict` 类型能打下半边天,这也使得很多的初学者不愿意花更多的时间去深入了解一些底层的实现原理(`源码`、`算法结构`),当然这不是必须的,况且对于很多的初学者来说,`Python`也只不过是工作中诸多工具中的一种,对的,`工具`。 22 | 23 | 但是如果你想让日常开发的代码更为`健壮`和`简洁`(`简洁即优美`不是吗),那就需要尝试着去了解一些 `Python` 的高阶用法,就算 24 | 仅仅是为了代码优化、提升运行效率,我也建议你能多去了解了解。 25 | 26 | 本教程注重实战,其中大部分代码也是笔者在日常工作中经常使用的,或经过反复验证的。 27 | 28 | 我希望通过大量通用的实例来帮助读者进行有效的实践,感受到 `Python` 为自动化办公带来的便利和效率。 29 | 30 | > 毋庸置疑的是,`Python` 仍然是`自动化办公`中的首选语言,对,`首选`。 -------------------------------------------------------------------------------- /docs/办公自动化/word/images/段落.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PY-GZKY/PythonAutomatedOfficeGuide/014509edfeff065588a1391c5a3e116976a40531/docs/办公自动化/word/images/段落.png -------------------------------------------------------------------------------- /docs/办公自动化/word/images/蔡水根.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PY-GZKY/PythonAutomatedOfficeGuide/014509edfeff065588a1391c5a3e116976a40531/docs/办公自动化/word/images/蔡水根.jpg -------------------------------------------------------------------------------- /docs/办公自动化/word/readme.md: -------------------------------------------------------------------------------- 1 | 2 | ### `Word` 3 | Word 文档作为 `Office` 三件套之一,在我们日常工作中不可谓不重要。 4 | 5 | 使用场景广泛,是协同工作和对接的主要渠道 !! 6 | 7 | 当然 `Word` 还应该有一个奇妙的作用,编写`离职申请`。 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /docs/办公自动化/word/一些案例.md: -------------------------------------------------------------------------------- 1 | ## 自动编写`离职申请` 2 | docx 在日常工作对接中是一个重要的传输和接收的文件载体,其中另一个妙用可能是编写离职申请。 3 | 我们都知道,事实上所谓离职申请的正文内容大都一致,最大的变量可能是 姓名、职称、时间这些个人信息, 4 | 所以,批量生成离职申请就变成了一件非常有趣的事情了。 5 | 6 | 首先我们找到一个离职模板 7 | ```python 8 | """此模板来自网络,做了小小改动 !!!""" 9 | 10 | lzStr = '''\t\t你好啊! 我({name})已决定于2050年4月15日正式离职,\n 11 | \t\t所以特提前半月向您提交这份辞职申请。\n 12 | \t\t还请留意一下简历中是否有适合接替我工作的人选,\n 13 | \t\t感激你这几年对我的照顾和帮忙,十分感激!\n 14 | \t\t最近在整理自我的简历,突然发觉这几年我在公司做的工种实在太多了,\n 15 | \t\t细数一下,有不下十种。\n 16 | \t\t先是1900年3月从前台开始做起,\n 17 | \t\t然后是人事,行政,市场开拓,婚庆,护肤品牌信息收集,再到之后的外贸。\n 18 | \t\t还有兼职文案,校对,送货,以及各种各样的手工。\n 19 | \t\t并且大多数时候我同时在做好几件事情,\n 20 | \t\t虽然我从来没有抱怨过我的工作太多可是这并不表示我的工作量小。\n 21 | \t\t并且我所有的工作都有做完的那天,或者是被人接手的那一天,\n 22 | \t\t这也就意味着一个新的工作即将来临。\n 23 | \t\t有时候就觉得自我是个高级打杂工,真的太杂了,\n 24 | \t\t杂到我此刻已经搞不清楚我自我能干什么,想干什么,\n 25 | \t\t我此刻对自我的职业定位和前程也是一片迷茫。\n 26 | \t\t所以,我此刻想休息一下,为自我的将来好好打算一下,\n 27 | \t\t重新规划自我的职业和人生。\n\n 28 | \t\t记得年前已经跟你谈过一次,谈过我的想法,\n 29 | \t\t所以想必我的辞职对你来说应当不会太意外。\n 30 | \t\t我是公司刚起步不久就过来帮你创业了,\n 31 | \t\t到此刻为止已经做了快3年多了,\n 32 | \t\t看着公司一天天成长壮大,慢慢的走上正轨我心里也是蛮高兴的。\n 33 | \t\t老实说离开公司多少还是有些不舍。我明白你的想法点子也很多,\n 34 | \t\t以前每一次你有新想法我都很支持你,\n 35 | \t\t都会全心全意按着你的想法去做事。\n 36 | \t\t公司出现了什么问题,我都会尽我最大的努力去帮你处理好\n 37 | \t\t因为我很重承诺,我答应的事情必须会做到。\n\n 38 | \t\t此刻公司已经走上正轨并且新鲜血液不断供给,\n 39 | \t\t我想我能够激流勇退了,也请你给我一次机会,\n 40 | \t\t一次让我实现自我想法的机会。\n 41 | \t\t{name}\t2050-04-15 04:15:15\n 42 | ''' 43 | ``` 44 | 45 | ```python 46 | 47 | # -*- coding: utf-8 -*- 48 | from docx import Document 49 | from docx.enum.text import WD_PARAGRAPH_ALIGNMENT 50 | from docx.oxml.ns import qn 51 | from docx.shared import RGBColor, Pt 52 | from docx.shared import Inches, RGBColor 53 | 54 | 55 | # 初始化一个文档 56 | document = Document() 57 | 58 | # 全局指定字体 59 | document.styles['Normal'].font.name = u'.萍方-简' 60 | document.styles['Normal']._element.rPr.rFonts.set(qn('w:eastAsia'), u'.萍方-简') 61 | 62 | # 加个标题 63 | paragraph = document.add_heading('离职申请', level=3) 64 | 65 | # 居中对齐 66 | paragraph_format = paragraph.paragraph_format 67 | paragraph_format.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER 68 | 69 | paragraph = document.add_paragraph() 70 | paragraph.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER 71 | run = paragraph.add_run("") 72 | run.add_picture('./image.jpg', width=Inches(1.0), height=Inches(1.0)) 73 | 74 | paragraph = document.add_paragraph() 75 | lz_add_run = paragraph.add_run(lzStr.format(name="吴老板")) 76 | lz_add_run.font.size = Pt(8) # 字体大小设置,和word里面的字号相对应 77 | lz_add_run.font.color.rgb = RGBColor(54, 95, 145) 78 | 79 | document.save('离职.docx') 80 | ``` 81 | ## 抓取豆瓣影评并写入 `docx` 文档 82 | ## `Word` 转 `PDF` 83 | > 为了不让我们的离职申请在别人打开时显得那么不那么 `LOW`,我们可以在自定义文档风格之后转换成 84 | `PDF` 文件,这样会舒服得多 ! -------------------------------------------------------------------------------- /docs/办公自动化/word/常用功能.md: -------------------------------------------------------------------------------- 1 | 2 | ## 安装`Python-docx` 3 | 4 | `docx` 并非 `Python` 的标准库,属于第三方扩展,我们通过 `pip` 命令安装: 5 | 6 | ```shell 7 | pip install python-docx 8 | ``` 9 | 10 | 如果因为网络或者其他原因导致 `pip` 无法正常安装 11 | 12 | 请访问 [https://pypi.org/project/python-docx/](https://pypi.org/project/python-docx/) 13 | 手动下载压缩文件并解压后安装 14 | 15 | ```shell 16 | tar xvzf python-docx-{version}.tar.gz 17 | cd python-docx-{version} 18 | python setup.py install 19 | ``` 20 | 21 | 22 | ## 快速开始 23 | ### 新建文档 24 | 25 | ```python 26 | from docx import Document 27 | document = Document() 28 | ``` 29 | 这一步非常简易,我们申明一个 `Document` 对象,如果不传入`docx`文档路径的话,默认 30 | 打开一个新的空白文档 31 | 32 | 这种使用缺省参数调起文档对象的方式大大简化了 33 | 我们的代码量,变得更加简单 34 | 35 | ### 保存文档 36 | 37 | ```python 38 | from docx import Document 39 | 40 | document = Document() 41 | document.save("./nihao.docx") 42 | ``` 43 | 44 | `save` 方法用于保存 `docx` 文档 45 | 46 | ### 尝试写入标题和内容 47 | #### 添加标题 48 | ```python 49 | from docx import Document 50 | document = Document() 51 | document.add_heading('地下交通站') 52 | document.add_heading('鼎香楼', level=2) 53 | # document.save("./地下交通站.docx") 54 | ``` 55 | 56 | 进入源码查看 `add_heading` 方法 57 | ```python 58 | def add_heading(self, text="", level=1): 59 | """Return a heading paragraph newly added to the end of the document. 60 | 61 | The heading paragraph will contain *text* and have its paragraph style 62 | determined by *level*. If *level* is 0, the style is set to `Title`. If *level* 63 | is 1 (or omitted), `Heading 1` is used. Otherwise the style is set to `Heading 64 | {level}`. Raises |ValueError| if *level* is outside the range 0-9. 65 | """ 66 | if not 0 <= level <= 9: 67 | raise ValueError("level must be in range 0-9, got %d" % level) 68 | style = "Title" if level == 0 else "Heading %d" % level 69 | return self.add_paragraph(text, style) 70 | ``` 71 | 可以看到 `add_heading` 方法接收了两个参数, 72 | `text`参数是标题文本,而`level`是等级大小,默认是 `一级标题` 73 | 74 | 75 | #### 添加段落 76 | 段落是 `docx` 文档中最主要块级对象,用于写入正文、图片、表格等等 77 | ```python 78 | paragraph = document.add_paragraph('贾贵原本是安丘城的一个地痞流氓') 79 | ``` 80 | 使用 `add_paragraph` 方法我们可以写入段落内容, 81 | 但是我们更多的是想给内容指定一些样式,自定义一些风格。 82 | 83 | `python-docx`支持大部分原生 `docx` 的文本样式 84 | ,诸如对齐方式、缩进、行间距、字体大小,字体风格,颜色等等 85 | 86 | 比如我们准备了一份`地下交通站`的经典语录 87 | ```text 88 | 我捂着脸撅起屁股就和他打起来了。 89 | 二十年后老子又是一条好汉……奸。 90 | 老子他妈今天不打人,老子今天他妈打你。 91 | 一流氓二土匪日本鬼子宪兵队,警备队侦缉队亲生儿子维持会。 92 | 建立王道乐土需要牲口,维护新秩序更需要牲口 总之皇军和牲口是不能分开的。 皇军会把牲口当成自己的兄弟。 93 | 我就知道那姑娘长的嘿。 94 | 你滴宝刀大大滴好,我滴,要了。你滴办公桌大大滴好,我滴,这里办公。你滴老婆大大滴漂亮,我滴…… 95 | 真是天下汉奸一般蠢。 96 | 对对对,我就是那狗屁贾队长。 97 | ``` 98 | 99 | 我们将其写入一段正文段落并加以修饰 100 | ```python 101 | from docx import Document 102 | from docx.enum.text import WD_PARAGRAPH_ALIGNMENT 103 | from docx.shared import Pt, RGBColor 104 | from docx.oxml.ns import qn 105 | 106 | document = Document() 107 | 108 | 109 | # 全局指定字体 110 | document.styles['Normal'].font.name = u'.萍方-简' 111 | document.styles['Normal']._element.rPr.rFonts.set(qn('w:eastAsia'), u'.萍方-简') 112 | 113 | header = document.add_heading('地下交通站',level=2) 114 | 115 | # 设置标题对齐方式为居中对齐 116 | header_format = header.paragraph_format 117 | header_format.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER 118 | 119 | text = ''' 120 | 我捂着脸撅起屁股就和他打起来了。 121 | 二十年后老子又是一条好汉……奸。 122 | 老子他妈今天不打人,老子今天他妈打你。 123 | 一流氓二土匪日本鬼子宪兵队,警备队侦缉队亲生儿子维持会。 124 | 建立王道乐土需要牲口,维护新秩序更需要牲口 总之皇军和牲口是不能分开的。 皇军会把牲口当成自己的兄弟。 125 | 我就知道那姑娘长的嘿。 126 | 你滴宝刀大大滴好,我滴,要了。你滴办公桌大大滴好,我滴,这里办公。你滴老婆大大滴漂亮,我滴…… 127 | 真是天下汉奸一般蠢。 128 | 对对对,我就是那狗屁贾队长。''' 129 | 130 | # 申明一个段落 131 | paragraph = document.add_paragraph() 132 | 133 | # 设置对齐方式为居中对齐 134 | paragraph_format = paragraph.paragraph_format 135 | paragraph_format.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER 136 | 137 | # 设置一个块对象 138 | run = paragraph.add_run(text) 139 | 140 | # 设置字体大小和颜色 141 | run.font.size = Pt(7) 142 | run.font.color.rgb = RGBColor(0x42, 0x24, 0xE9) # RGB 143 | 144 | document.save("./地下交通站.docx") 145 | ``` 146 | 147 | #### 做了什么 148 | 149 | - 在 `document` 对象中设置了全局字体,**注意: 如果是`docx`不自带的字体风格,需要自行下载字体到本地以供选择** 150 | - 设置了一个`二级标题` 151 | - 通过 `paragraph_format` 属性设置了对齐方式 152 | - 通过 `add_run` 添加块元素并插入准备好的文本 153 | - 设置了正文内容的字体大小和颜色 154 | - 保存文档 155 | 156 | 文档效果如下 157 | 158 | ![](./images/段落.png) 159 | 160 | 161 | ## 插入图片 162 | ```python 163 | from docx.shared import Inches 164 | document.add_picture('./盛世美颜贾队长.png', width=Inches(1.0)) 165 | ``` 166 | 167 | 通过 `add_picture` 方法添加一张`本地图片`到文档, 168 | 默认`docx` 库不支持解析添加在线图片地址。 169 | 170 | 所以如果我们想要添加在线图片的话可以 171 | 先通过获取该图片的`二进制流`内容,再通过 `add_picture` 方法添加到文档中 172 | 173 | ```python 174 | import requests,io 175 | from docx.shared import Inches 176 | url = 'https://www.easyicon.net/api/resizeApi.php?id=1311353&size=128' 177 | io_url = io.BytesIO(requests.get(url).content) 178 | # 添加一张图片 179 | document.add_picture(io_url, width=Inches(1.0)) 180 | ``` 181 | 182 | ## 插入表格 183 | 在`docx`文档中是可以插入和操作表格的 184 | ```python 185 | table = document.add_table(rows=2, cols=2) 186 | ``` 187 | 188 | 使用 `add_table` 方法添加了一个表格,我们试着通过表格的一些属性和方法来操作单元格 189 | 190 | ```python 191 | cell = table.cell(0, 1) 192 | cell.text = '嘿,打起来了嘿' 193 | ``` 194 | `.text` 属性可以给单元格赋值 (赋值或修改) 195 | 196 | - `0` 表示第一行 197 | - `1` 表示第二列 198 | 199 | > 这里表格的行和列索引都是从0开始的 200 | 201 | 指定写入行的单元格,通过 `rows` 属性指定某一行或某些行。然后通过行的 `cells` 属性给单元格赋值 202 | 203 | ```python 204 | row = table.rows[2] 205 | row.cells[0].text = '前几天最热的时候我们家热的快炸了' 206 | row.cells[1].text = '那你开空调啊' 207 | ``` 208 | - `2` 表示指定了表格中的第 `3` 行 209 | - `0` 表示写入了该行的第 `1` 列 210 | - `1` 表示写入了该行的第 `2` 列 211 | 212 | 通过 `len` 函数获取表格中行和列的总数 213 | ```python 214 | row_count = len(table.rows) 215 | col_count = len(table.columns) 216 | ``` 217 | 218 | #### 逐步添加行和列 219 | 当我们无法确定目前文档表格的行数时,可以选择实时添加行或列, 220 | 这样可以灵活控制表格的长度和宽度,减少不必要的空行或空列 221 | 222 | 223 | 比如我们不知道数据列表的长度或是需要实时添加行操作, 224 | 参考以下代码 225 | 226 | ```python 227 | items = [ 228 | {"name":"贾贵","desc":"侦缉队队长","createDate":"2021-04-20"}, 229 | {"name":"黄金标","desc":"警备队队长","createDate":"2021-04-21"}, 230 | {"name":"黑藤","desc":"特务机关长","createDate":"2021-04-22"}, 231 | {"name":"孙友福","desc":"鼎香楼掌柜","createDate":"2021-04-23"}, 232 | {"name":"水根","desc":"鼎香楼大伙计","createDate":"2021-04-24"} 233 | ] 234 | 235 | # 添加表 236 | table = document.add_table(1, 3) 237 | table.style='Medium Grid 1 Accent 1' 238 | 239 | # 表头 240 | heading_cells = table.rows[0].cells 241 | heading_cells[0].text = '名字' 242 | heading_cells[1].text = '职务' 243 | heading_cells[2].text = '创建时间' 244 | 245 | for item in items: 246 | cells = table.add_row().cells 247 | cells[0].text = item["name"] 248 | cells[1].text = item["desc"] 249 | cells[2].text = item["createDate"] 250 | ``` 251 | 252 | - 添加了一个 `1` 行 `3` 列的表格,作为添加`表头`用 253 | - 为表格添加一点样式 `Medium Grid 1 Accent 1` 254 | - 使用 `add_row` 方法动态添加行操作 255 | 256 | > 关于表格样式列表可以参考 [https://www.cnblogs.com/AbnerLc/p/13375707.html](https://www.cnblogs.com/AbnerLc/p/13375707.html) 257 | 258 | 我们还可以通过 `len(items[0])` 得到 `items` 列表中字典的长度来作为列的长度 259 | 260 | ```python 261 | # 添加表 262 | table = document.add_table(1, len(items[0])) 263 | ``` 264 | ## 页眉和页脚 265 | 有时候我们会给文档添加页眉和注脚 266 | 267 | #### 添加页眉 268 | ```python 269 | document = Document() 270 | section = document.sections[0] 271 | header = section.header 272 | paragraph = header.paragraphs[0] 273 | paragraph.text = "鼎香楼" 274 | # paragraph.text = "左对齐文本\t居中文本\t右对齐文本" 275 | paragraph.style = document.styles["Header"] 276 | ``` 277 | 278 | 声明一个 `section` 并使用 `header` 属性为文档添加`页眉` 279 | 280 | #### 添加页脚 281 | ```python 282 | footer = section.footer 283 | paragraph = footer.paragraphs[0] 284 | # 居中显示 285 | paragraph.text = "\t来自地下交通站\t" 286 | ``` 287 | 288 | 只需将 `header` 换成 `footer`,为文档添加`页脚` 289 | 290 | > 这里只是列举了 `python-docx` 库的一些常用功能, 291 | > 更多的文档样式和使用方法读者可以使用 `python-docx` 的官方文档 292 | > [https://python-docx.readthedocs.io](https://python-docx.readthedocs.io) 293 | 294 | > 祝你有个好心情 !! -------------------------------------------------------------------------------- /docs/办公自动化/定时任务/基本用法.md: -------------------------------------------------------------------------------- 1 | > 我还是认为有必要谈一谈 `定时任务`, 它在日常工作中太常见了。 2 | 3 | ## Celery - 分布式任务队列 4 | 5 | `celery` 是一个非常强大 `python` 第三方任务队列。 6 | 7 | 运用于日常任务的生产和消费,以及后台`定时任务`。 8 | 9 | 用官方文档的原话说 ,Celery 是一个简单,灵活,可靠的分布式系统,用于处理大量消息,同时为操作提供维护此类系统所需的工具。 10 | 11 | 它是一个任务队列,专注于实时处理,同时还支持任务调度。 12 | 13 | Celery 是用 Python 编写的,但协议可以用任何语言实现。 14 | 15 | 除了 Python 之外,还有 Node.js 和 PHP 客户端。 16 | 17 | 18 | ## 生产者消费者模式 19 | ### Celery的架构 20 | 21 | Celery的架构由三部分组成,消息中间件(message broker),任务执行单元(worker)和任务执行结果存储(task result store)组成。 22 | 23 | ### 消息中间件 24 | 25 | celery消息中间件,也就是所谓的中间人,官方支持的两种稳定的消息队列数据库,一个是RabbitMQ,另一个 Redis。当然选用其他数据库也是可行的,比如, MongoDB 等,用的比较多的当然就是高性能Redis数据库啦,当然MQ也同样强大。 Worker 进程会持续监视队列中是否有需要处理的新任务(如果有就消费,没有则持续监听队列) 26 | 27 | ### 任务执行单元 28 | 29 | Worker 是 Celery 提供的任务执行的单元,Worker 并发的运行在分布式的系统节点中,也就是充当了任务工人的角色(消费者),用于系统调度。 30 | 31 | 在开启中间消息队列之后,任务单元会监听消息队列并从中间件里消费任务,执行任务单元,将结果存入后端数据库。 32 | 33 | ### 任务结果存储 34 | 35 | Celery 支持以不同方式存储任务的结果,后端存储包括 Redis,MongoDB,Mysql 等等。 36 | 37 | ## 安装 38 | 39 | ```python 40 | pip install celery 41 | pip install redis 42 | ``` 43 | 44 | 45 | ## 基础用法 46 | 47 | ### 编写任务 48 | 49 | 这里我们编写 2 个方法(加和减),通过 装饰器注册为 celery 任务。 50 | 51 | ```python 52 | # tasks.py 53 | from celery import Celery 54 | 55 | celery_app = Celery('my_task', broker='redis://localhost:6379/0') 56 | 57 | @celery_app.task 58 | def add(x, y): 59 | print("等待 20 s .... done") 60 | time.sleep(20) # 模拟耗时操作 61 | return x + y 62 | 63 | @celery_app.task 64 | def mu(x, y): 65 | print("等待 5 s .... done") 66 | time.sleep(5) # 模拟耗时操作 67 | return x - y 68 | 69 | ``` 70 | 71 | ### 启动 worker 72 | ```python 73 | celery -A tasks worker -l info -c 2 -P eventlet 74 | celery -A tasks beat -l info 75 | ``` 76 | 77 | 现在很困、不想写了。 78 | 79 | 80 | ### 发布任务 81 | 82 | ### 消费任务 83 | 84 | ### 定时任务 85 | -------------------------------------------------------------------------------- /docs/办公自动化/数据处理/files/msyhbd.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PY-GZKY/PythonAutomatedOfficeGuide/014509edfeff065588a1391c5a3e116976a40531/docs/办公自动化/数据处理/files/msyhbd.ttf -------------------------------------------------------------------------------- /docs/办公自动化/数据处理/files/pic/1.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PY-GZKY/PythonAutomatedOfficeGuide/014509edfeff065588a1391c5a3e116976a40531/docs/办公自动化/数据处理/files/pic/1.jpeg -------------------------------------------------------------------------------- /docs/办公自动化/数据处理/files/pic/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PY-GZKY/PythonAutomatedOfficeGuide/014509edfeff065588a1391c5a3e116976a40531/docs/办公自动化/数据处理/files/pic/1.jpg -------------------------------------------------------------------------------- /docs/办公自动化/数据处理/files/stopwords: -------------------------------------------------------------------------------- 1 | 此用户没有填写评价。 2 | 可以 3 | 请问 4 | 什么 5 | 如何 6 | 不过 7 | 时候 8 | 没有 9 | 有点 10 | 一般 11 | 知道 12 | 怎么样 13 | 这个 14 | 不会 15 | 一下 16 | 大家 17 | 有没有 18 | 你们 19 | 怎么 20 | 还是 21 | 多少 22 | 但是 23 | 自己 24 | 不是 25 | 应该 26 | 觉得 27 | 就是 28 | 咋样 29 | 30 | -------------------------------------------------------------------------------- /docs/办公自动化/数据处理/images/诠释词云.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PY-GZKY/PythonAutomatedOfficeGuide/014509edfeff065588a1391c5a3e116976a40531/docs/办公自动化/数据处理/images/诠释词云.jpg -------------------------------------------------------------------------------- /docs/办公自动化/数据处理/images/诠释词频.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PY-GZKY/PythonAutomatedOfficeGuide/014509edfeff065588a1391c5a3e116976a40531/docs/办公自动化/数据处理/images/诠释词频.jpg -------------------------------------------------------------------------------- /docs/办公自动化/数据处理/readme.md: -------------------------------------------------------------------------------- 1 | > 数据处理 !! -------------------------------------------------------------------------------- /docs/办公自动化/数据处理/图表展示/readme.md: -------------------------------------------------------------------------------- 1 | > 你好 -------------------------------------------------------------------------------- /docs/办公自动化/数据处理/图表展示/词云.md: -------------------------------------------------------------------------------- 1 | ![logo](https://www.easyicon.net/api/resizeApi.php?id=1214577&size=96) 2 | 3 | > 利用 `Python` 生成词云图 4 | 5 | ![Python](https://img.shields.io/badge/Python-3.7+-blue) 6 | ![Wordcloud](https://img.shields.io/badge/wordcloud-1.8.0-brightgreen) 7 | ![Pandas](https://img.shields.io/badge/pandas-1.1.1-red) 8 | ![Matplotlib](https://img.shields.io/badge/matplotlib-3.3.2-blue) 9 | ![Jieba](https://img.shields.io/badge/jieba-0.42.1-green) 10 | 11 | `词云图`能更直接美观的呈现出文本中不同词语出现的次数,也就是`词频`, 12 | 所以人们总是热衷于用这样的`词云图`来诠释图表的意义: 13 | ![诠释词云](../images/诠释词云.jpg ':size=36%') 14 | 15 | 确实,这种展示图表的方式的确能够引起大众的共鸣,直接、美观、有趣 !! 16 | 17 | ## 安装依赖库 18 | 19 | 我们需要安装一些必要的依赖来让 `Python` 正常工作。 20 | 21 | ```shell 22 | pip install Pandas 23 | pip install Jieba 24 | pip install Wordcloud 25 | pip install Matplotlib 26 | ``` 27 | 28 | ## 准备数据 29 | 我们需要一些用以分析的数据集,这些数据集一般可以是 `Execl`表格文件、`csv`表格文件、甚至是 `txt` 文本文件,或者从 30 | 我们的`数据库`中获取,比如 `Mysql`、`MongoDB` 等等。 31 | 32 | 这里准备了一份 Excel表格作为我们的初始数据集 [关键词提取.xlsx](./files/关键词提取.xlsx) 33 | 34 | 35 | ## 开始码字 36 | 37 | ### 读取数据集 38 | ```python 39 | # -*- coding: utf-8 -*- 40 | import jieba 41 | import matplotlib.pyplot as plt 42 | from wordcloud import WordCloud 43 | import pandas as pd 44 | import os 45 | 46 | def read_content(df): 47 | w_contents = [] 48 | h_contents = [] 49 | for i in range(len(df)): 50 | w_con = str(df["问题内容"][i]) 51 | h_con = str(df["回答内容"][i]) 52 | print("问题内容",w_con) 53 | print("回答内容",h_con) 54 | w_contents.append(w_con) 55 | h_contents.append(h_con) 56 | return {'w_contents': w_contents,'h_contents':h_contents} 57 | 58 | f = pd.ExcelFile('../问大家词频统计.xlsx') 59 | 60 | data = pd.DataFrame() 61 | for i in f.sheet_names: 62 | df = pd.read_excel('../问大家词频统计.xlsx', sheet_name=i) 63 | print(i.split('|')[-1]) 64 | content_dict = read_content(df) 65 | print(content_dict) 66 | ``` 67 | 68 | 读取到内容如下: 69 | ```text 70 | 问题内容 这个和暗影有啥区别啊?为啥都在推荐暗影 71 | 回答内容 暗影上游戏比光影强一些,游戏性能比光影强 72 | 问题内容 这个和暗影有啥区别啊?为啥都在推荐暗影 73 | 回答内容 。。。。 74 | 问题内容 这个和暗影有啥区别啊?为啥都在推荐暗影 75 | 回答内容 暗影比这更游戏本 76 | 问题内容 这个和暗影有啥区别啊?为啥都在推荐暗影 77 | 回答内容 推荐暗影,当个等等党,新的暗影要出了。暗影比光影好点。 78 | 问题内容 这个和暗影有啥区别啊?为啥都在推荐暗影 79 | 回答内容 不懂 80 | 问题内容 这个和暗影有啥区别啊?为啥都在推荐暗影 81 | 回答内容 不懂,但是好几次开不开机,客服说是静电原因 82 | 问题内容 打CF CSGO 吃鸡怎么样啊,卡不卡啊?CAD画图啥的没问题吧。 83 | 回答内容 贼卡,建议别买 84 | 问题内容 打CF CSGO 吃鸡怎么样啊,卡不卡啊?CAD画图啥的没问题吧。 85 | 回答内容 微卡, 86 | 问题内容 打CF CSGO 吃鸡怎么样啊,卡不卡啊?CAD画图啥的没问题吧。 87 | 回答内容 没有 88 | 问题内容 室内设计用i7还是i5ACD还有其他设计软件好用嘛? 89 | 回答内容 不要买!才用两个月各种死机!感觉用不到一年就会报废。 90 | 问题内容 室内设计用i7还是i5ACD还有其他设计软件好用嘛? 91 | 回答内容 毕竟是光影精灵不是暗影精灵,建议买暗影精灵 92 | 问题内容 室内设计用i7还是i5ACD还有其他设计软件好用嘛? 93 | 回答内容 别买 94 | 问题内容 室内设计用i7还是i5ACD还有其他设计软件好用嘛? 95 | 回答内容 别买 卡了 不到一个月 96 | 问题内容 室内设计用i7还是i5ACD还有其他设计软件好用嘛? 97 | 回答内容 不太好吧 98 | 问题内容 有提示升更新或者激活Windows吗 99 | 回答内容 有的开机就会提示 100 | 问题内容 有提示升更新或者激活Windows吗 101 | ``` 102 | 103 | > 这说明我们可以正常的读取到初使数据集中的数据,有了一个好的开头 !! 104 | 105 | ### 设置分词 106 | 107 | 在展示词云图之前,我们需要将文本内容进行分词,这里用到了 `jieba` 组件,`jieba`组件分词相对准确且支持多种分词模式, 108 | 在此场景中可能是最佳选择。 109 | 110 | ```python 111 | def wordCloudShow(contents,index,stopwords,font): 112 | # 准确分词 113 | contents_after_split = jieba.cut(str(contents), cut_all=False) 114 | 115 | # 只获取长度大于等于2的字符,也就是单个文字不构成分析条件 116 | words = ' '.join([i for i in contents_after_split if len(i) >= 2]) 117 | 118 | # 设置屏蔽词,引入屏蔽词文件 119 | STOPWORDS = set(map(str.strip, open(stopwords,encoding="utf-8").readlines())) 120 | 121 | 122 | # 导入背景图 123 | bg_image = plt.imread(f'../pic/1.jpg') 124 | 125 | # 设置词云参数,参数分别表示:画布宽高、背景颜色、背景图形状、字体,屏蔽词、最大词的字体大小 126 | wc = WordCloud(background_color='white', 127 | mask=bg_image, 128 | font_path=font, 129 | stopwords=STOPWORDS, 130 | max_font_size=200, 131 | random_state=100) 132 | 133 | # 将分词后数据传入云图 134 | wc.generate_from_text(words) 135 | plt.imshow(wc) 136 | plt.axis('off') # 不显示坐标轴 137 | plt.show() # show 出来 138 | 139 | # 保存结果到本地 140 | wc.to_file(f'../../词云图/问题内容/{index}.jpg') 141 | ``` 142 | 143 | 可以看到,在此方法中我们将传入 144 | `content` (文本内容)、 145 | `index` (生成图片的序号)、 146 | `stopwords` (屏蔽词)、 147 | `font` (指定字体) 148 | 149 | ### `stopwords` 150 | 151 | `屏蔽词`文件,这里我们以 `stopwords` 命名,顾名思义就是我们不希望一些无意义的,对文本分析不用的词出现在我们展示的词云图上, 152 | 需要将它们过滤屏蔽掉。 153 | 154 | `WordCloud` 对象中提供了这么一个参数,可以自定义 `stopwords` 过滤掉一些无意义的词。 155 | 156 | 比如`什么`、`亲`、`该用户默认好评`这些对我们分析无积极作用的词,这样我们将得到一份具有积极意义的分词列表。 157 | 158 | 159 | > 最终代码 160 | 161 | ```python 162 | # -*- coding: utf-8 -*- 163 | import jieba 164 | import matplotlib.pyplot as plt 165 | from wordcloud import WordCloud 166 | import pandas as pd 167 | import os 168 | 169 | def read_content(df): 170 | w_contents = [] 171 | h_contents = [] 172 | for i in range(len(df)): 173 | w_con = str(df["问题内容"][i]) 174 | h_con = str(df["回答内容"][i]) 175 | print("问题内容",w_con) 176 | print("回答内容",h_con) 177 | w_contents.append(w_con) 178 | h_contents.append(h_con) 179 | return {'w_contents': w_contents,'h_contents':h_contents} 180 | 181 | 182 | def wordCloudShow(contents,index,stopwords,font): 183 | 184 | contents_after_split = jieba.cut(str(contents), cut_all=False) # 准确分词 185 | words = ' '.join([i for i in contents_after_split if len(i) >= 2]) 186 | STOPWORDS = set(map(str.strip, open(stopwords,encoding="utf-8").readlines())) 187 | bg_image = plt.imread(f'../pic/1.jpg') 188 | wc = WordCloud(background_color='white', 189 | mask=bg_image, 190 | font_path=font, 191 | stopwords=STOPWORDS, 192 | max_font_size=200, 193 | random_state=100) 194 | 195 | wc.generate_from_text(words) 196 | plt.imshow(wc) 197 | plt.axis('off') 198 | plt.show() 199 | wc.to_file(f'{index}.jpg') 200 | 201 | 202 | f = pd.ExcelFile('问大家词频统计.xlsx') 203 | 204 | data = pd.DataFrame() 205 | for i in f.sheet_names: 206 | df = pd.read_excel('问大家词频统计.xlsx', sheet_name=i) 207 | print(i.split('|')[-1]) 208 | content_dict = read_content(df) 209 | wordCloudShow(content_dict['w_contents'], i.split('|')[-1], "./stopwords", "./msyhbd.ttf") 210 | ``` 211 | -------------------------------------------------------------------------------- /docs/办公自动化/数据处理/图表展示/词频.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | > 利用 `Python` 生成词云图 4 | 5 | ![Python](https://img.shields.io/badge/Python-3.7+-blue) 6 | ![Wordcloud](https://img.shields.io/badge/wordcloud-1.8.0-brightgreen) 7 | ![Pandas](https://img.shields.io/badge/pandas-1.1.1-red) 8 | ![Matplotlib](https://img.shields.io/badge/matplotlib-3.3.2-blue) 9 | ![Jieba](https://img.shields.io/badge/jieba-0.42.1-green) 10 | 11 | `词频分析` 实际上是一种条形图,横竖条形图视情况而定 !! 12 | 13 | ![诠释词云](../images/诠释词频.jpg ':size=50%') 14 | 15 | ## 安装依赖库 16 | 17 | 我们需要安装一些必要的依赖来让 `Python` 正常工作。 18 | 19 | ```shell 20 | pip install Pandas 21 | pip install Jieba 22 | pip install Wordcloud 23 | pip install Matplotlib 24 | ``` 25 | 26 | ## 准备数据 27 | 我们需要一些用以分析的数据集,这些数据集一般可以是`Execl`表格文件、`csv`表格文件、甚至是 `txt` 文本文件,或者从 28 | 我们的`数据库`中获取,比如 `Mysql`、`MongoDB` 等等。 29 | 30 | 这里准备了一份 `Excel`表格作为我们的初始数据集 [关键词提取.xlsx](./files/关键词提取.xlsx) 31 | 32 | ## 开始码字 33 | 34 | > 最终代码 35 | 36 | ```python 37 | # -*- coding: utf-8 -*- 38 | import os 39 | import jieba 40 | from collections import Counter 41 | import json 42 | import matplotlib.pyplot as plt 43 | import pandas as pd 44 | from matplotlib import ticker 45 | 46 | def read_content(df): 47 | contents = ',' 48 | for i in range(len(df)): 49 | l_con = str(df["评论内容"][i]) 50 | i_con = str(df["追加评论"][i]) if str(df["追加评论"][i]) != "nan" else "" 51 | # print("评论: ",l_con) 52 | # print("追加评论: ",i_con) 53 | print("评论+追评: ", l_con + i_con) 54 | contents += l_con + i_con 55 | 56 | return {'contents': contents} 57 | 58 | 59 | def txt2plt(contents, index): 60 | 61 | # 字体风格 62 | plt.rcParams['font.sans-serif'] = ['SimHei'] 63 | plt.rcParams['font.family'] = 'sans-serif' 64 | 65 | # 虽然没有屏蔽词文件,但是我们可以用列表的形式过滤掉一些词 66 | _words = [x for x in jieba.cut(contents) if 67 | len(x) >= 2 and x not in [ 68 | "这里", "就是", "无用", "的词"]] 69 | 70 | c = Counter(_words).most_common(15) # 取15组 71 | print(json.dumps(c, ensure_ascii=False)) 72 | 73 | name_list = [x[0] for x in c] # X轴的值 74 | num_list = [x[1] for x in c] # Y轴的值 75 | plt.barh(name_list, num_list, alpha=0.6) 76 | plt.xlabel('次数') 77 | plt.ylabel('词语') 78 | plt.title(f'{index} - 文本分词统计', loc='center', fontsize='15',fontweight='bold') 79 | plt.savefig(f"./{index}.jpg") 80 | plt.show() 81 | 82 | 83 | f = pd.ExcelFile('关键词提取.xlsx') 84 | 85 | data = pd.DataFrame() 86 | for i in f.sheet_names: 87 | df = pd.read_excel('关键词提取.xlsx', sheet_name=i) 88 | content_dict = read_content(df) 89 | txt2plt(content_dict['contents'], i) 90 | ``` 91 | 92 | 93 | -------------------------------------------------------------------------------- /docs/办公自动化/文件处理/m3u8音视频拼接.md: -------------------------------------------------------------------------------- 1 | > https://play.fenyucn.com/m/t/S3NstazCETxIoEu8l-KSp22b1LdPkjr99oLGTZlGReo6g2Y-OfC7fL61CMCBtVPGCErpCGvSs3ipdA--jCJgLXoXGbz_3GdJu4PAIfGzIjWYgLVetPLZAfeP9OL5QUAF.m3u8 2 | 3 | `m3u8` 是一种又 `.ts` 音视频`片段`拼凑而成的文件,当浏览器加载 `.m3u8` 文件时,我们可以看到后台 `ajax` 是分一段一段加载的, 4 | 也就是看到哪加载到哪。 5 | 6 | `m3u8` 内容大致如下 7 | ```text 8 | #EXTM3U 9 | #EXT-X-VERSION:3 10 | #EXT-X-TARGETDURATION:5 11 | #EXT-X-MEDIA-SEQUENCE:0 12 | #EXTINF:5.000000, 13 | https://t.fenyucn.com//s18/M00/A1/87/CoUBYGB7E7iESQbUAAAAADjXebE233_transcode_133947_0.ts 14 | #EXTINF:5.000000, 15 | https://t.fenyucn.com//s18/M00/A1/87/CoUBYGB7E7iESQbUAAAAADjXebE233_transcode_133947_1.ts 16 | #EXTINF:5.000000, 17 | https://t.fenyucn.com//s18/M00/A1/87/CoUBYGB7E7iESQbUAAAAADjXebE233_transcode_133947_2.ts 18 | #EXTINF:5.000000, 19 | https://t.fenyucn.com//s18/M00/A1/87/CoUBYGB7E7iESQbUAAAAADjXebE233_transcode_133947_3.ts 20 | #EXTINF:5.000000, 21 | https://t.fenyucn.com//s18/M00/A1/87/CoUBYGB7E7iESQbUAAAAADjXebE233_transcode_133947_4.ts 22 | #EXTINF:5.000000, 23 | https://t.fenyucn.com//s18/M00/A1/87/CoUBYGB7E7iESQbUAAAAADjXebE233_transcode_133947_5.ts 24 | #EXTINF:5.000000, 25 | https://t.fenyucn.com//s18/M00/A1/87/CoUBYGB7E7iESQbUAAAAADjXebE233_transcode_133947_6.ts 26 | #EXTINF:5.000000, 27 | https://t.fenyucn.com//s18/M00/A1/87/CoUBYGB7E7iESQbUAAAAADjXebE233_transcode_133947_7.ts 28 | #EXTINF:5.000000, 29 | https://t.fenyucn.com//s18/M00/A1/87/CoUBYGB7E7iESQbUAAAAADjXebE233_transcode_133947_8.ts 30 | #EXTINF:2.466667, 31 | https://t.fenyucn.com//s18/M00/A1/87/CoUBYGB7E7iESQbUAAAAADjXebE233_transcode_133947_9.ts 32 | #EXT-X-ENDLIST 33 | ``` 34 | 35 | > 其中有用的就是像 `https://t.fenyucn.com//s18/M00/A1/87/CoUBYGB7E7iESQbUAAAAADjXebE233_transcode_133947_9.ts` 的 .ts 链接了。 36 | 37 | 我们需要将它下载下来,这里我提前存到本地。 38 | 39 | #### 提取 `.ts` 片段 40 | ```python 41 | # m3u8是本地的文件路径 42 | def get_ts_urls(m3u8_path): 43 | urls = [] 44 | with open(m3u8_path, "r") as file: 45 | lines = file.readlines() 46 | for line in lines: 47 | if line.endswith(".ts\n"): 48 | print(line) 49 | urls.append(line.strip("\n")) 50 | return urls 51 | ``` 52 | 53 | #### 下载 `.ts` 片段到指定文件夹 54 | ```python 55 | import datetime 56 | import time 57 | import os 58 | import requests 59 | 60 | def download(ts_urls, download_path): 61 | for i in range(len(ts_urls)): 62 | ts_url = ts_urls[i] 63 | file_name = ts_url.split("/")[-1] 64 | print("开始下载 %s" % file_name) 65 | try: 66 | response = requests.get(ts_url, stream=True, verify=False) 67 | except Exception as e: 68 | print("异常请求:%s" % e.args) 69 | return 70 | ts_path = download_path + "/{0}.ts".format(i) 71 | with open(ts_path, "wb+") as file: 72 | for chunk in response.iter_content(chunk_size=1024): 73 | if chunk: 74 | file.write(chunk) 75 | 76 | time.sleep(0.78) 77 | ``` 78 | 79 | #### 文件夹取出 `.ts` 文件并 `排序` 80 | ```python 81 | import os 82 | def file_walker(path): 83 | file_list = os.listdir(path) 84 | # file_list.sort() 85 | file_list.sort(key=lambda x: int(x[:-3])) 86 | file_list_ = [] 87 | for fn in file_list: 88 | p = str("tsfiles" + '/' + fn) 89 | file_list_.append(p) 90 | 91 | return file_list_ 92 | ``` 93 | 94 | #### 将 `.ts` 重新拼接到大文件中(`ts`/`mp4`) 95 | 96 | ```python 97 | def combine(ts_path, file_name): 98 | file_list = file_walker(ts_path) 99 | file_path = file_name + '.MP4' 100 | with open(file_path, 'wb+') as fw: 101 | for i in range(len(file_list)): 102 | fw.write(open(file_list[i], 'rb').read()) 103 | 104 | if __name__ == '__main__': 105 | urls = get_ts_urls("hhh.m3u8") 106 | download(urls, "tsfiles") 107 | combine("tsfiles", "大理") 108 | ``` 109 | 110 | ```python 111 | import datetime 112 | import time 113 | import os 114 | 115 | import requests 116 | 117 | # m3u8是本地的文件路径 118 | def get_ts_urls(m3u8_path): 119 | urls = [] 120 | with open(m3u8_path, "r") as file: 121 | lines = file.readlines() 122 | for line in lines: 123 | if line.endswith(".ts\n"): 124 | print(line) 125 | urls.append(line.strip("\n")) 126 | return urls 127 | 128 | 129 | def download(ts_urls, download_path): 130 | for i in range(len(ts_urls)): 131 | ts_url = ts_urls[i] 132 | file_name = ts_url.split("/")[-1] 133 | print("开始下载 %s" % file_name) 134 | try: 135 | response = requests.get(ts_url, stream=True, verify=False) 136 | except Exception as e: 137 | print("异常请求:%s" % e.args) 138 | return 139 | 140 | ts_path = download_path + "/{0}.ts".format(i) 141 | with open(ts_path, "wb+") as file: 142 | for chunk in response.iter_content(chunk_size=1024): 143 | if chunk: 144 | file.write(chunk) 145 | time.sleep(.56) 146 | 147 | 148 | def file_walker(path): 149 | file_list = os.listdir(path) 150 | # file_list.sort() 151 | file_list.sort(key=lambda x: int(x[:-3])) 152 | file_list_ = [] 153 | for fn in file_list: 154 | # print(fn) 155 | p = str("tsfiles" + '/' + fn) 156 | file_list_.append(p) 157 | 158 | print(file_list_) 159 | return file_list_ 160 | 161 | 162 | def combine(ts_path, file_name): 163 | file_list = file_walker(ts_path) 164 | file_path = file_name + '.MP4' 165 | with open(file_path, 'wb+') as fw: 166 | for i in range(len(file_list)): 167 | fw.write(open(file_list[i], 'rb').read()) 168 | 169 | 170 | if __name__ == '__main__': 171 | urls = get_ts_urls("hhh.m3u8") 172 | download(urls, "tsfiles") 173 | combine("tsfiles", "大理") 174 | ``` 175 | 176 | 177 | > 其中容易有变数的可能是 `.m3u8` 文件中的 `.ts` 链接有时候是不带`后缀`的,需要重新判断。 178 | 179 | 180 | #### 多线程版本 181 | ```python 182 | # encoding:utf-8 183 | import time 184 | import m3u8 185 | import requests 186 | from glob import iglob 187 | from natsort import natsorted 188 | from urllib.parse import urljoin 189 | from dataclasses import dataclass 190 | from concurrent.futures import ThreadPoolExecutor 191 | 192 | 193 | @dataclass 194 | class DownLoad_M3U8(object): 195 | m3u8_url: str 196 | file_name: str 197 | 198 | def __post_init__(self): 199 | self.headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36', } 200 | self.threadpool = ThreadPoolExecutor(max_workers=10) 201 | 202 | if not self.file_name: 203 | self.file_name = 'new.mp4' 204 | 205 | def get_ts_url(self): 206 | m3u8_obj = m3u8.load(self.m3u8_url) 207 | base_uri = m3u8_obj.base_uri 208 | 209 | for seg in m3u8_obj.segments: 210 | yield urljoin(base_uri, seg.uri) 211 | 212 | def download_single_ts(self, urlinfo): 213 | url, ts_name = urlinfo 214 | res = requests.get(url, headers=self.headers) 215 | with open(ts_name, 'wb') as fp: 216 | fp.write(res.content) 217 | 218 | def download_all_ts(self): 219 | ts_urls = self.get_ts_url() 220 | for index, ts_url in enumerate(ts_urls): 221 | print(ts_url) 222 | self.threadpool.submit(self.download_single_ts, [ts_url, f'./ts片段/{index}.ts']) 223 | self.threadpool.shutdown() 224 | 225 | def run(self): 226 | self.download_all_ts() 227 | ts_path = f'./ts片段/*.ts' 228 | with open(self.file_name, 'ab') as fn: 229 | for ts in natsorted(iglob(ts_path)): 230 | with open(ts, 'rb') as ft: 231 | scline = ft.read() 232 | fn.write(scline) 233 | 234 | # for ts in iglob(ts_path): 235 | # os.remove(ts) 236 | 237 | 238 | if __name__ == '__main__': 239 | m3u8_url = '' 240 | file_name = '' 241 | start = time.time() 242 | M3U8 = DownLoad_M3U8(m3u8_url, file_name) 243 | M3U8.run() 244 | end = time.time() 245 | print('耗时:', end - start) 246 | 247 | ``` -------------------------------------------------------------------------------- /docs/办公自动化/文件处理/python视频转音频.md: -------------------------------------------------------------------------------- 1 | ### You-Get 2 | ```shell 3 | pip install you-get 4 | ``` 5 | 6 | #### Info 7 | ```shell 8 | you-get -i "https://www.bilibili.com/video/BV1rq4y1V7ht" 9 | ``` 10 | 11 | #### download 12 | ```shell 13 | you-get --format=flv720 "https://www.bilibili.com/video/BV1rq4y1V7ht" 14 | ``` 15 | 首先通过 `you-get` 下载一个 `b站` 的视频 16 | ```shell 17 | Downloading 点弦泛音高能!《江南》美爆的「指弹吉他」!林俊杰听了都想点赞!.flv ... 18 | 100% ( 43.8/ 43.8MB) ├████████████████████████████████████████┤[1/1] 319 kB/s 19 | 20 | Downloading 点弦泛音高能!《江南》美爆的「指弹吉他」!林俊杰听了都想点赞!.cmt.xml ... 21 | ``` 22 | 23 | - `.flv` 视频文件(指定其他格式如 `MP4`) 24 | - .`cmt.xml` 弹幕文件 25 | 26 | ### moviepy 27 | 28 | ```shell 29 | pip install moviepy 30 | ``` 31 | 32 | `视频`转换为`音频`文件 33 | ```python 34 | from moviepy.editor import * 35 | 36 | video = VideoFileClip('点弦泛音高能!《江南》美爆的「指弹吉他」!林俊杰听了都想点赞!.flv') 37 | audio = video.audio 38 | audio.write_audiofile('点弦泛音高能!《江南》美爆的「指弹吉他」!林俊杰听了都想点赞!.mp3') 39 | ``` 40 | 41 | ```shell 42 | MoviePy - Writing audio in 点弦泛音高能!《江南》美爆的「指弹吉他」!林俊杰听了都想点赞!.mp3 43 | MoviePy - Done. 44 | ``` -------------------------------------------------------------------------------- /docs/办公自动化/文件处理/readme.md: -------------------------------------------------------------------------------- 1 | > `公司同事`:有一大批表格文件需要重命名,手动代价太大,有没有快速批量给文件重命名呢? 2 | 3 | > `Python`:我都不用第三方扩展,`os` 模块可以在 `1` 秒之内帮你全部搞定 4 | 5 | ```python 6 | # -*- coding: utf-8 -*- 7 | import os 8 | path = "C:\\Users\\HP\\Desktop\\test" 9 | 10 | def rename(oldName,newName): 11 | filelist=os.listdir(path) 12 | for files in filelist: 13 | oldFilePath = os.path.join(path,files) 14 | if os.path.isdir(oldFilePath): 15 | continue 16 | print(oldFilePath) 17 | newFilePath = os.path.join(path, f"{os.path.splitext(files)[0].replace(oldName,newName)}{os.path.splitext(files)[1]}") 18 | print(newFilePath) 19 | os.rename(oldFilePath, newFilePath) 20 | 21 | rename("Nice","你好") 22 | ``` -------------------------------------------------------------------------------- /docs/办公自动化/文件处理/图像压缩.md: -------------------------------------------------------------------------------- 1 | 2 | ```python 3 | import os 4 | from PIL import Image 5 | 6 | Image.MAX_IMAGE_PIXELS = 2300000000 7 | 8 | # 获取文件大小:KB 9 | def get_size(file): 10 | size = os.path.getsize(file) 11 | return size / 1024 12 | 13 | 14 | def compress_image(infile, outfile=None, mb=3000, step=40, quality=90): 15 | """ 16 | :param infile: 压缩源文件 17 | :param outfile: 压缩文件保存地址 18 | :param mb: 压缩目标,KB 19 | :param step: 每次调整的压缩比率 20 | :param quality: 初始压缩比率 21 | :return: 压缩文件地址,压缩文件大小 22 | """ 23 | if outfile is None: 24 | outfile = infile 25 | o_size = get_size(infile) 26 | 27 | if o_size <= mb: 28 | im = Image.open(infile) 29 | im.save(outfile) 30 | 31 | while o_size > mb: 32 | im = Image.open(infile) 33 | im.save(outfile, quality=quality) 34 | if quality - step < 0: 35 | break 36 | quality -= step 37 | o_size = get_size(outfile) 38 | 39 | 40 | if __name__ == '__main__': 41 | compress_image(infile="./images/80_18__.jpg") 42 | ``` 43 | 44 | > 如果图像文件过大 PIL 会报超出最大限制大小的错误,增加 `Image.MAX_IMAGE_PIXELS = 2300000000` 或者 45 | > `Image.MAX_IMAGE_PIXELS = None` 忽略此参数 -------------------------------------------------------------------------------- /docs/办公自动化/文件处理/图像拼接.md: -------------------------------------------------------------------------------- 1 | 2 | ### 通过 `PIL + numpy` 数组拼接图像 3 | ```python 4 | # -*- coding:utf-8 -*- 5 | from PIL import Image 6 | Image.MAX_IMAGE_PIXELS = 2300000000 7 | 8 | import numpy as np 9 | 10 | paths = ["./images/80_18.jpg", "./images/80_18_.jpg", "./images/80_18__.jpg"] 11 | img_array = '' 12 | img = '' 13 | for i, v in enumerate(paths): 14 | if i == 0: 15 | img = Image.open(v) 16 | img_size = (int(img.size[0] / 2), int(img.size[1] / 2)) 17 | img = img.resize(img_size, Image.ANTIALIAS) 18 | img_array = np.array(img) # 转化为np array对象 19 | if i > 0: 20 | img = Image.open(v) 21 | img_size = (int(img.size[0] / 2), int(img.size[1] / 2)) 22 | img = img.resize(img_size, Image.ANTIALIAS) 23 | img_array2 = np.array(img) 24 | img_array = np.concatenate((img_array, img_array2), axis=1) # 横向拼接 25 | # img_array = np.concatenate((img_array, img_array2), axis=0)# 纵向拼接 26 | img = Image.fromarray(img_array) 27 | 28 | img.save('./horizontal.jpg', quality=40) 29 | ``` -------------------------------------------------------------------------------- /docs/办公自动化/文件处理/字符串相似度匹配.md: -------------------------------------------------------------------------------- 1 | 2 | ## difflib 3 | ```python 4 | import difflib 5 | def get_equal_rate1(str1, str2): 6 | return difflib.SequenceMatcher(None, str1, str2).quick_ratio() 7 | 8 | 9 | import Levenshtein 10 | def get_equal_rate2(str1, str2): 11 | return Levenshtein.ratio(str1, str2) 12 | 13 | 14 | print(get_equal_rate1(str1="北京路", str2="北京路商业步行街")) 15 | print(get_equal_rate2(str1="北京路步行街", str2="北京路商业步行街")) 16 | ``` -------------------------------------------------------------------------------- /docs/办公自动化/自然语言处理/readme.md: -------------------------------------------------------------------------------- 1 | > 来了 -------------------------------------------------------------------------------- /docs/办公自动化/自然语言处理/文本翻译.md: -------------------------------------------------------------------------------- 1 | ## translators 2 | `translators` 算是一个支持语言比较多、使用门槛比较低的一个良心库了。 3 | 4 | 最初是为大学生做国际化使用的。 5 | 6 | ```shell 7 | pip install translators 8 | ``` 9 | 10 | ```python 11 | import translators as ts 12 | 13 | chs_text = '于谦的父亲郭老爷子。' 14 | en_text = "Yu Qian's father, father Guo." 15 | 16 | # professional field 17 | print(ts.alibaba(en_text, from_language='auto', to_language='zh', professional_field='general')) 18 | print(ts.caiyun(chs_text, from_language='zh', professional_field=None)) 19 | ``` 20 | 21 | ## 阿里云开放平台翻译 API 22 | 首先安装所需的依赖库 23 | ```shell 24 | pip install aliyun-python-sdk-alimt 3.1.1 25 | pip install aliyun-python-sdk-core 2.13.35 26 | pip install aliyun-python-sdk-core-v3 2.13.32 27 | ``` 28 | 29 | 阿里云账号在开放平台中获取 `AccessKey ID` 和 `Access Key Secret` 30 | 31 | ```python 32 | # coding:utf-8 33 | import json 34 | 35 | from aliyunsdkalimt.request.v20181012 import TranslateRequest 36 | from aliyunsdkcore.client import AcsClient 37 | 38 | # 创建AcsClient实例 39 | client = AcsClient( 40 | "", # 阿里云账号的 AccessKey ID 41 | "", # 阿里云账号 Access Key Secret 42 | "cn-hangzhou" # 地域ID 43 | ) 44 | request = TranslateRequest.TranslateRequest() 45 | 46 | 47 | def longrunning_recognize(en_text): 48 | # 创建request,并设置参数 49 | request.set_SourceLanguage("en") # 源语言 50 | request.set_Scene("description") # 设置场景,商品标题:title,商品描述:description,商品沟通:communication 51 | request.set_SourceText(en_text) # 原文 52 | request.set_FormatType("text") # 翻译文本的格式 53 | request.set_TargetLanguage("zh") # 目标语言 54 | request.set_method("POST") 55 | # 发起API请求并显示返回值 56 | response = client.do_action_with_exception(request) 57 | return json.loads(response) 58 | 59 | longrunning_recognize("hello world !!") 60 | ``` 61 | 62 | 阿里开放的`翻译api`准确率和可读性比较相对不错的。 63 | 64 | 当然其他诸如`百度开放平台`、`搜狗开放平台`、`腾讯开放平台`都有翻译 `api`, 免费版的翻译数量当然有所限制。 65 | 66 | 我们甚至可以从 `百度`和`谷歌`翻译首页上直接调试 `post` 请求获取返回的结果。这种方式免费且无限制、但翻译结果稍差。 -------------------------------------------------------------------------------- /docs/办公自动化/自然语言处理/音频转文字.md: -------------------------------------------------------------------------------- 1 | ## 阿里开放平台音频转文字 2 | 阿里云账号在开放平台中获取 `AccessKey ID` 、 `Access Key Secret` 和 `appkey` 3 | 4 | > appkey 需要创建应用实例后获取 5 | 6 | ```shell 7 | pip install aliyun-python-sdk-alimt 3.1.1 8 | pip install aliyun-python-sdk-core 2.13.35 9 | pip install aliyun-python-sdk-core-v3 2.13.32 10 | ``` 11 | 12 | ```python 13 | # -*- coding: utf8 -*- 14 | import requests 15 | from aliyunsdkcore.client import AcsClient 16 | from aliyunsdkcore.request import CommonRequest 17 | 18 | def get_token(): 19 | # 创建AcsClient实例 20 | client = AcsClient( 21 | "", # 阿里云账号的AccessKey ID 22 | "", # 阿里云账号Access Key Secret 23 | "cn-shanghai" # 地域ID 24 | ) 25 | 26 | # 创建request,并设置参数。 27 | request = CommonRequest() 28 | request.set_method('POST') 29 | request.set_domain('nls-meta.cn-shanghai.aliyuncs.com') 30 | request.set_version('2019-02-28') 31 | request.set_action_name('CreateToken') 32 | response = client.do_action_with_exception(request) 33 | return response.decode() 34 | 35 | 36 | # token_obj = get_token() 37 | # print(token_obj) 38 | 39 | # todo 下载音频,注意 url 格式,小心有坑 40 | def get_content(url): 41 | headers = { 42 | "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9", 43 | "accept-encoding": "gzip, deflate, br", 44 | "accept-language": "zh-CN,zh;q=0.9", 45 | "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.93 Safari/537.36", 46 | } 47 | req = requests.get(url, headers=headers, verify=False, timeout=120) 48 | # todo 这里的音频有可能无法顺利下载,导致上传的空内容,做了一下判断 49 | if req.status_code == 200: 50 | if len(req.content) < 200: 51 | return False 52 | return req.content 53 | else: 54 | return None 55 | 56 | 57 | def longrunning_recognize(token): 58 | headers = { 59 | "Content-type": "application/octet-stream", 60 | "Content-Length": "8600000000", 61 | "Host": "nls-gateway.cn-shanghai.aliyuncs.com" 62 | } 63 | params = { 64 | "appkey": "", 65 | "format": "wav", 66 | "sample_rate": 16000, 67 | "token": token 68 | } 69 | i_content = get_content(url="https://mjtt.gowithtommy.com/mjtt_backend_server/prod/data/c11bba8f6ee178060360b30c02793a5952ac4eac.mp3") 70 | # i_content = open("nh.mp3", "rb") # 本地图片 71 | files = { 72 | "file": i_content 73 | } 74 | response = requests.post( 75 | url='https://nls-gateway.cn-shanghai.aliyuncs.com/stream/v1/FlashRecognizer', 76 | headers=headers, 77 | params=params, 78 | files=files, 79 | timeout=600 80 | ) 81 | return response.json() 82 | 83 | 84 | longrunning_recognize_result = longrunning_recognize(token="c8fc8f43f356416d9554a697e6c294db") 85 | # print(longrunning_recognize_result) 86 | longrunning_recognize_result = [i.get("text") for i in longrunning_recognize_result.get("flash_result").get("sentences")] 87 | longrunning_recognize_result = "".join(longrunning_recognize_result) 88 | print(longrunning_recognize_result) 89 | ``` 90 | 91 | 92 | -------------------------------------------------------------------------------- /docs/快速初始化/installer.md: -------------------------------------------------------------------------------- 1 | 2 | ## 安装 `Python` 3 | 4 | 基于 `Anaconda` 搭建 `Python` 环境 5 | 6 | 7 | ### 安装`Anaconda` 8 | 9 | 不管是`Windows`或`Linux(Mac)` 系统都推荐使用`Anaconda`搭建`Python`环境,这是一个绝佳的选择。 10 | 11 | `Windows` 下安装 `Anaconda` 看起来只需要傻瓜式操作,最后再把它放入环境变量即可,这里不在详述 :smile: 12 | 13 | ### `Linux` 下安装`Anaconda` 14 | 15 | 这里以`Centos7`为例 16 | ```shell 17 | $ yum update && 18 | yum -y install wget && 19 | wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh && 20 | bash ./Miniconda3-latest-Linux-x86_64.sh 21 | ``` 22 | 23 | 由于某些原因,默认的`anaconda` 站点`wget`可能会比较慢,可以选择国内的镜像源平台进行安装,比如清华源就很优秀。 24 | 25 | > [清华镜像站](https://mirrors.tuna.tsinghua.edu.cn/anaconda/miniconda/) 26 | 27 | 选择你适应的平台和系统版本,`wget` 下载至本地即可。 28 | 29 | ```shell 30 | $ wget https://mirrors.tuna.tsinghua.edu.cn/anaconda/archive/Anaconda3-2019.03-Linux-x86_64.sh 31 | ``` 32 | 33 | 至于安装步骤,一路按确认键即可,配置环境变量并让它生效 34 | 35 | ```shell 36 | vi /etc/profile 37 | ``` 38 | 在文件最尾端加入一下路径,比如我把安装目录放在了 `root` 下 39 | 40 | ```shell 41 | PATH=$PATH:/root/anaconda3/bin 42 | export PATH 43 | ``` 44 | ```shell 45 | $ source /etc/profile 46 | ``` 47 | 48 | 查看当前的 Python 环境 49 | ```shell 50 | [root@gzky_gz ~] conda --version 51 | conda 4.9.2 52 | [root@gzky_gz ~] python 53 | Python 3.7.9 (default, Aug 31 2020, 12:42:55) 54 | [GCC 7.3.0] :: Anaconda, Inc. on linux 55 | Type "help", "copyright", "credits" or "license" for more information. 56 | >>> 57 | 58 | ``` 59 | 60 | > 至此,Anaconda安装完毕 !! 61 | 62 | 63 | ### `pip` 包管理工具 64 | 65 | 我们使用 `pip` 作为`Python`的包管理工具,安装 `anaconda` 完毕之后,我们 66 | 可以在黑窗口中键入 67 | `pip -V` 来查看 pip 版本 68 | 69 | ## 安装依赖库 70 | 71 | 在 Python 中,我们可以通过 pip 工具进行第三方库的安装,这也是最快、最简单的安装方式。 72 | 73 | ```shell 74 | pip install PyMysql 75 | ``` 76 | 77 | 但是由于一些第三方扩展存放于海外服务器,由于众所周知的原因我们无法通过正常手段获取,可以使用 -i 参数 78 | 指定源,加速安装扩展库: 79 | ```shell 80 | pip install PyMysql -i https://pypi.tuna.tsinghua.edu.cn/simple 81 | ``` 82 | 83 | ## `Python` 虚拟环境 84 | 85 | 成功安装 `Python` 之后默认只提供一个版本,比如 `Python3.7` 86 | 87 | > 这对于习惯切换多版本开发的小伙伴可就不太友好了 !! 88 | 89 | 所以,为了兼容不同`Python`版本共同开发,我们需要使用 `Python` 虚拟环境 90 | 91 | 由于我们前面是使用 `conda` 搭建的 `Python` 环境,而 `conda` 也提供了 `Python`的虚拟环境,可以切换到不同的`Python` 92 | 版本进行开发,不同版本之间相互隔离,是一个独立的操作环境 !! 93 | 94 | 这里假设读者本地默认安装了 `Python3.7` 版本 95 | 96 | ### 创建一个新的虚拟环境 97 | 98 | ```shell 99 | # Python 3.6 100 | $ conda create -n venv_3.6 python=3.6 101 | 102 | # Python 3.8 103 | $ conda create -n venv_3.8 python=3.8 104 | ``` 105 | 106 | 其中 `-n` 指定了虚拟环境的名称,可以随便起,后面跟着 `Python` 和指定`版本号` 107 | 108 | 查看创建的虚拟环境列表 109 | 110 | ```shell 111 | > conda env list 112 | # conda environments: 113 | # 114 | base * D:\conda3 115 | venv_3.6 D:\conda3\envs\venv_3.6 116 | venv_3.8 D:\conda3\envs\venv_3.8 117 | ``` 118 | 119 | > 可以看到刚才创建的环境已经有了 120 | 121 | ### 激活并进入虚拟环境 122 | 123 | ```shell 124 | Linux: source activate venv 125 | Windows: activate venv 126 | ``` 127 | 128 | ### 安装 `pipenv` 129 | 130 | ```shell 131 | pip install pipenv 132 | ``` 133 | 134 | 进入新的虚拟环境会发现很多第三方工具和扩展包需要重新安装,比如 `PyMysql` 等等 135 | 136 | 想往常一样执行 `pip install pymysql` 安装第三方扩展即可 137 | 138 | ### 删除一个现有的虚拟环境 139 | 140 | ```shell 141 | # 将清空所有 142 | conda remove --name venv --all 143 | ``` 144 | 145 | > 注意,如果是`Pycharm`编辑器的话可以将设置中的项目控制中的`Python`解释器路径更改为 `venv` 的路径,更改生效后,这就意味着你已经进入了新的`Python`版本环境 146 | 147 | 148 | ## `CentOS` 系统 `python` 默认版本 `python2` 改为 `python/python3` 149 | 150 |  CentOS中如果安装有yum,一般会有python2的某个版本。 151 | 152 | 一般而言如果没有环境覆盖之前、命令行键入`python`,出现的是`python2`的环境 153 | 154 | 155 | ```shell 156 | [root@instance-hrnebyqu src]# python 157 | Python 2.7.5 (default, Apr 11 2018, 07:36:10) 158 | [GCC 4.8.5 20150623 (Red Hat 4.8.5-28)] on linux2 159 | Type "help", "copyright", "credits" or "license" for more information. 160 |   161 | ``` 162 | 163 | 实际上输入 `python/python2` 出来的环境都是 `python2` 版本。而这显然不是我们想要的。 164 | 165 | 166 | 我们希望将`python`这个`shell`指令连接到`python3`的版本。 167 | 168 | 这里首先装`python3`,然后将`python`连接到`python3`上。 169 | 170 | 本质上就是一个软链接的指向。 171 | 172 | 由于路径添加到了`bash_profile`文件中的`PATH`中了,因此`环境变量`不需要再改了。 173 | 174 | 175 | ```shell 176 | vim ~/.bash_profile 177 | ``` 178 | 把 `Python3` 的路径加上,然后重载 `bash_profile`这个文件 179 | 180 | 接着修改bashrc这个文件 181 | ```shell 182 | vim ~/.bashrc 183 | ``` 184 | 185 | 将 `python2` 和 `python3` 的路径都写上,并将 `python` 指定为 `python3` 186 | 187 | ```shell 188 | alias python2=/usr/bin/python 189 | alias python3=/usr/local/python3/bin/python3 190 | alias python=/usr/local/python3/bin/python3 191 | ``` 192 | 193 | 当然了,如果你安装的事 `conda` 环境,这里加入你的安装路径是 `/root/miniconda3/bin/python` 194 | 那么: 195 | ```shell 196 | alias python2=/usr/bin/python 197 | alias python=/root/miniconda3/bin/python 198 | ``` 199 | 200 | 最后让他生效即可 201 | ```shell 202 | source ~/.bashrc 203 | ``` 204 | > 这样,命令行开 python 就是 python3 的环境了。 205 | 206 | ```shell 207 | [root@instance-hrnebyqu src]# python 208 | Python 3.9.0 (default, Jul 4 2019, 12:00:29) 209 | [GCC 4.8.5 20150623 (Red Hat 4.8.5-36)] on linux 210 | Type "help", "copyright", "credits" or "license" for more information.   211 | ``` 212 | 213 | 214 | ## 编辑器 215 | 216 | 在 `Python` 中我们推荐使用 `Pycharm` 编辑器来进行代码开发,这也是现在最主流的`Python` 编辑器了。 217 | 218 | 如果你经常和数据表格打交道,我强烈建议你使用 `Jupyter NoteBook`。 219 | 220 | 221 | 222 | ## `Node` | `Npm` 223 | 224 | > `nodejs`: [https://nodejs.org/en/download](https://nodejs.org/en/download/) 225 | 226 | 这里主要说说 `Linux` 下如何安装 `Node` 和 `Npm`,`windows` 安装 `node` 不再赘述。 227 | 228 | - 进入官方下载页面后,根据自己系统选择 `Linux Binaries (x64)` 或 `Linux Binaries (ARM)`,我这里是 229 | `Linux Binaries (x64)`,下载到本地后`解压`即可。 230 | 231 | - `Linux` 一般的软件存放目录是 `/opt`,我们把它存在这里。 232 | - 软链接到 `/usr/local/bin/` 目录,为了能够全局作用与 `node` 和 `npm`,我们还需要将其软链接到 `/usr/local/bin/` 目录 233 | 234 | 235 | ```shell 236 | sudo ln -s /opt/node/bin/node /usr/local/bin/node 237 | sudo ln -s /opt/node/bin/npm /usr/local/bin/npm 238 | ``` 239 | 240 | - 如果`软链接`不可用或失效,可以直接将 `/opt/node` 加入到环境变量中 241 | 242 | ```shell 243 | vi /etc/profile 244 | ``` 245 | 246 | 添加至最后一行即可 247 | ```shell 248 | export PATH=$PATH:/opt/node/bin 249 | ``` 250 | 251 | 生效 252 | ```shell 253 | source /etc/profile 254 | ``` 255 | 256 | 验证,查看版本信息 257 | ```shell 258 | node -V 259 | npm -V 260 | ``` 261 | -------------------------------------------------------------------------------- /docs/快速初始化/readme.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ### 操作系统 4 | 5 | 本教程环境基于 `Windows` 环境,笔者认为 `Linux` 并不适合作为 `Python` 自动化的操作平台, 6 | 至少不是自动化操作最好的系统,`Windows` 系统使用用户居三个操作系统之首,受众面广,软件兼容性较强, 7 | 可视化操作相比 `Linux` 系统简单易用得多,这也是受普遍大量用户青睐的重要原因。 8 | 9 | 当然 `Mac` 系统的交互性和美观性不在 `Windows` 之下,但就如上面所说的,受众面不够广,`10`个人里大概有 `7`个人使用 `Windows` 系统, 10 | `2`个人使用 `Mac` 系统,还有`1`个人使用 `Linux` 系统,当然`Mac`系统使用的也是基于 `Unix` 的内核,这里不做详细对比。 11 | 12 | 13 | ### 认识`Python` 14 | 15 | `Python` 是一种易于学习又功能强大的编程语言。它提供了高效的高级数据结构,还能简单有效地面向对象编程。`Python` 优雅的语法和动态类型,以及解释型语言的本质,使它成为多数平台上写脚本和快速开发应用的理想语言。 16 | 17 | `Python` 解释器易于扩展,可以使用 `C` 或 `C++`(或者其他可以通过 `C` 调用的语言)扩展新的功能和数据类型。`Python` 也可用于可定制化软件中的扩展程序语言。 18 | 19 | 目前`Python`分为多版本,`Python2` 已宣布不在维护,虽然信奉 `Python2` 的人还不在少数,但我还是建议你使用`Python3`(3.6以上版本)。 20 | 21 | 22 | ### 你需要学习什么 23 | 24 | - `Python`集成和开发环境 25 | - 数据结构 26 | - 语言基础 27 | - 常用关键字 28 | - 自动化相关库操作 29 | 30 | 31 | ### `RPA`工具 32 | 33 | `Python`对于自动化来说确实是一把利刃,但是有一定的开发门槛。 34 | 35 | 而`RPA`软件则是把这个脚本代码流程转换成了流程化、拖拽式的操作,一般而言,在代码中实现的一些语言结构上的东西都能用`RPA`工具来编写。 36 | 37 | 这是流程化操作层面的,当然绝大多数的`RPA`软件也支持编码模式,只是支持编码的语言不尽相同,有的支持.Net编程,有的支持`Python`编程,还有一些自定义的逻辑语言。这也是当下自动化工具和编程语言结合的一种工作模式,自动化流程完成不了的用编码填补,或是控件自动化流程过长,则用编码代替。 38 | 39 | 国内的`影刀`,`阿里RPA`都支持`Python扩展`,并且可以在应用时配合使用,控件和编码的连线,让自动化省去了更多的障碍。 40 | 41 |
42 | 43 | > 可惜这里`影刀`和`阿里RPA` 并没有给我赞助,所以就不铺开谈了 ..... -------------------------------------------------------------------------------- /docs/数据库/MongoDB/images/Mongodb数据库工具.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PY-GZKY/PythonAutomatedOfficeGuide/014509edfeff065588a1391c5a3e116976a40531/docs/数据库/MongoDB/images/Mongodb数据库工具.png -------------------------------------------------------------------------------- /docs/数据库/MongoDB/images/选择Mongodb版本.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PY-GZKY/PythonAutomatedOfficeGuide/014509edfeff065588a1391c5a3e116976a40531/docs/数据库/MongoDB/images/选择Mongodb版本.png -------------------------------------------------------------------------------- /docs/数据库/MongoDB/安装MongoDB.md: -------------------------------------------------------------------------------- 1 | ## Centos7 安装 Mongodb 2 | 3 | 有必要将安装数据库环境依赖写在前面、 4 | > [MongoDB 官网下载地址](https://www.mongodb.com/try/download/community) 5 | 6 | #### 选择合适的数据库版本 7 | 8 | 比如这里选择最新版的 `5.0.1` 版本`红帽`系统、也就是 `centos7.0` 系统啦 9 | 10 | --- 11 | 12 | ![选择Mongodb版本](./images/选择Mongodb版本.png) 13 | 14 | #### 解压到指定目录 15 | 16 | 将下载下来的 `tgz` 二进制安装包拖进指定要安装的目录并解压,这里我将其放在 `/usr/local/mongodb5.0.1` 17 | 18 | ```shell 19 | $ tar zxvf mongodb-linux-x86_64-5.0.1.tgz 20 | ``` 21 | 22 | #### 配置环境变量 23 | 24 | ```shell 25 | $ vim /etc/profile 26 | ``` 27 | 28 | 在其最后加入一下路径即可,问题不大 29 | 30 | ```shell 31 | export PATH=/usr/local/mongodb5.0.1/bin:$PATH 32 | ``` 33 | 34 | 使环境变量生效 35 | 36 | ```shell 37 | source /etc/profile 38 | ``` 39 | 40 | #### 创建存放数据目录 41 | 42 | ```shell 43 | $ cd /usr/local/mongodb5.0.1 44 | $ touch mongodb.conf 45 | $ mkdir db 46 | $ mkdir log 47 | $ cd log 48 | $ touch mongodb.log 49 | ``` 50 | 51 | #### 修改/添加 `mongodb` 配置文件 52 | 53 | ```shell 54 | vim /usr/local/mongodb5.0.1/mongodb.conf 55 | ``` 56 | 57 | ```shell 58 | port=27017 #端口 59 | dbpath= /usr/local/mongodb5.0.1/db #数据库存文件存放目录 60 | logpath= /usr/local/mongodb5.0.1/log/mongodb.log #日志文件存放路径 61 | logappend=true #使用追加的方式写日志 62 | fork=true # 以守护进程的方式运行,创建服务器进程 63 | maxConns=100 #最大同时连接数 64 | noauth=true #不启用验证 65 | journal=true #每次写入会记录一条操作日志(通过journal可以重新构造出写入的数据)。 66 | storageEngine=wiredTiger #存储引擎有mmapv1、wiretiger、mongorocks 67 | bind_ip = 0.0.0.0 #这样就可外部访问了,例如从win10中去连虚拟机中的MongoDB 68 | ``` 69 | 70 | #### 启动测试 `mongodb` 71 | 72 | 使用我们上面配置文件启动 `mongodb`,本地测试时最好不使用守护进程的方式,将 `fork` 改成 `false` 即可 73 | 74 | ```shell 75 | $ bin/mongod --config /usr/local/mongodb5.0.1/mongodb.conf 76 | ``` 77 | 78 | 此时我们键入在同目录下 `./mongo` 发现可以成功进入数据库,如果非守护进程的方式直接 ctrl+c 退出即可 79 | 80 | #### 设置数据库用户名、密码 81 | 82 | 我们一般会在远程服务器上搭建 `mongodb` 数据库,设置用户名和密码必不可少,否则会出现巨大的风险。 83 | 84 | 比如一些用户名和密码为空(或是弱口令)的数据库在运行一段时间后全库被清空并留下的一句话,请 支付 `0.2 btc` 到某账户之类的,这些人每时每刻都在寻到认为失误的空档,一旦数据失窃,后果不堪设想。 85 | 86 | 添加一个`root`权限 87 | 88 | ```shell 89 | use admin 90 | db.createUser({user:"admin",pwd:"ni_bu_zhi_dao",roles:["root"]}) 91 | // 验证是否成功,返回1 92 | db.auth("admin", "ni_bu_zhi_dao") 93 | ``` 94 | 95 | 比较推崇的做法是再创建一个管理员账户 96 | 97 | ```shell 98 | use admin 99 | db.createUser({ user: "admin", pwd: "ni_bu_zhi_dao", roles: [{ role: "userAdminAnyDatabase", db: "admin" }] }) 100 | db.auth("admin", "ni_bu_zhi_dao") 101 | ``` 102 | 103 | 当然也可以为某个数据库添加一个用户,不造成权限的滥用引发不必要后果 104 | 105 | ```shell 106 | use nice 107 | db.createUser({user: "nice_admin", pwd: "nice_123", roles: [{ role: "dbOwner", db: "nice" }]}) 108 | ``` 109 | 110 | 开启认证。 111 | 112 | 在此前的配置文件的最后加一个认证参数 `auth = true` 113 | 114 | 并去掉前面的 `noauth = true` 即免认证(当然你在本地测试的时候可以开启) 115 | 116 | 启动 `mongodb` 117 | 118 | ```shell 119 | port=27017 #端口 120 | dbpath= /usr/local/mongodb5.0.1/db #数据库存文件存放目录 121 | logpath= /usr/local/mongodb5.0.1/mongodb.log #日志文件存放路径 122 | logappend=true #使用追加的方式写日志 123 | fork=true #以守护进程的方式运行,创建服务器进程 124 | maxConns=100 #最大同时连接数 125 | journal=true #每次写入会记录一条操作日志(通过journal可以重新构造出写入的数据)。 126 | storageEngine=wiredTiger #存储引擎有mmapv1、wiretiger、mongorocks 127 | bind_ip = 0.0.0.0 #这样就可外部访问了,例如从win10中去连虚拟机中的MongoDB 128 | auth = true #用户认证 129 | ``` 130 | 131 | 此时通过远程连接 `mongodb` 数据库的时候会出现用户名、密码验证。 132 | 133 | > 如果你忘记了 `mongodb` 的密码 ???? 134 | 135 | 首先注释去掉 `mongodb.conf` 的 `auth` 项,登陆 `mongodb` 136 | 137 | ```shell 138 | use admin 139 | db.system.users.find() 140 | db.system.users.remove({}) 141 | ``` 142 | 143 | 重新设置密码即可 144 | 145 | ```shell 146 | db.createUser({user:"admin",pwd:"ni_bu_zhi_dao",roles:["root"]}) 147 | // 验证是否成功,返回1 148 | db.auth("admin", "ni_bu_zhi_dao") 149 | ``` 150 | 151 | #### 可视化管理工具 152 | 153 | `Mongodb` 官方推荐使用 `compass` 作为管理工具,这个工具界面清爽、操作简单、虽然也支持导入导出,然 功能相比其他数据库工具还是稍显不足,我并不将它视为第一选择。 154 | 155 | 要知道好的 `软件/工具` 再加以娴熟使用能使我们的工作事半功倍、效率奇佳。 156 | 157 | 现在比较主流的、用的比较多的数据库工具应该是 `Navicat` ,它支持当下一些主流的关系型数据库 和非关系型数据库,比如 `Mysql` 和 `Mongodb` 等等,推荐使用 `Navicat` 。 158 | 159 | `DataGrip` 也可以作为一种选择,以编辑器的风格编写 `SQL` 语句或逐渐成为我的一种习惯。 其功能也比较齐全。 160 | 161 | 需要知道的是 `Navicat` 是收费的,`DataGrip` 也需要解码才能无限制的使用。 162 | 163 | 当然我们也会有一些左门旁道得到`破解版`和`无限时激活`的方法,嘿嘿你懂 ~~ 164 | 165 | ![](./images/Mongodb数据库工具.png) 166 | 167 | ## Win 安装 Mongodb 168 | 169 | `windows` 平台安装 `Mongodb` 相对简单,不再赘述。 170 | 171 | 172 | 173 | -------------------------------------------------------------------------------- /docs/数据库/MongoDB/实际应用.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PY-GZKY/PythonAutomatedOfficeGuide/014509edfeff065588a1391c5a3e116976a40531/docs/数据库/MongoDB/实际应用.md -------------------------------------------------------------------------------- /docs/数据库/Mysql/Orm.md: -------------------------------------------------------------------------------- 1 | ## 面向对象的数据库编程 2 | 除了使用 `PyMysql`、`MysqlDB` 这些库之外,还有一种被称之为 `orm` 的概念,即`面向对象`的数据库编程。 3 | 4 | 在 `Python` 中我们经常使用 `Sqlalchemy` 5 | 6 | 7 | ## Sqlalchemy 8 | 9 | 简单用法如下 10 | ```python 11 | from sqlalchemy import create_engine 12 | from sqlalchemy.ext.declarative import declarative_base 13 | from sqlalchemy import Column,String,Integer 14 | from sqlalchemy.orm import sessionmaker 15 | from sqlalchemy_utils import database_exists,create_database 16 | 17 | # 1)连接数据库引擎 18 | engine = create_engine('mysql+pymysql://root:123456@localhost:3306/blog?charset=utf8') 19 | 20 | # 2)创建基类 21 | Base = declarative_base() 22 | 23 | # 3)初始化session 24 | Session = sessionmaker(bind=engine) 25 | 26 | # 4)创建session对象 27 | session = Session() 28 | 29 | # 5)创建数据库 30 | if not database_exists(engine.url): 31 | create_database(engine.url) 32 | 33 | ``` 34 | 35 | ### `模型类`/`实体类` 36 | 37 | 数据库中的表在编程语言中的体现,其本质就是一个 `python` 的`模型类`(在`java`和其他语言中称为`实体类`)。属性要与数据库表中的列相对应。 38 | 39 | ### 语法 40 | 41 | 大致语法如下 42 | ```shell 43 | class MODELNAME(Base): 44 | __tablename__ = 'TABLENAME' 45 | COLUMN_NAME = Column(TYPE,OPTIONS) 46 | ``` 47 | 48 | - MODELNAME: 定义模型名称,根据表名设定 49 | - TABLENAME: 映射到数据库中表的名字 50 | - COLUMN_NAME: 属性名,映射到表中列的名字 51 | - db.TYPE: 映射到列的数据类型 52 | - OPTIONS: 列选项 53 | 54 | #### `db.TYPE` 列类型 55 | 类型 python类型 说明 56 | Integer int 普通整数,32位 57 | SmallInteger int 小范围整数,通常16位 58 | BigInteger int/long 不限精度的整数 59 | Float float 浮点数 60 | Numeric decimal.Decimal 定点数 61 | String str 变长字符串 62 | Text str 变长字符串,优化 63 | Unicode unicode 变长Unicode字符串 64 | UnicodeText unicode 优化后的变长Unicode字符串 65 | Boolean bool 布尔值 66 | Date datetime.date 日期 67 | Time datetime.time 时间 68 | DateTime datetime.datetime 日期和时间 69 | 70 | #### `OPTIONS` 列选项 71 | primary_key 主键 72 | unique 唯一约束 73 | index 创建索引 74 | nullable 运行为空 75 | default 设置默认值 76 | 77 | #### 基本使用 78 | ```python 79 | class User(Base): 80 | __tablename__ = 'user' 81 | id = Column(Integer,primary_key = True) 82 | username = Column(String(30),nullable=Flase) 83 | ``` 84 | 85 | - 声明了一个用户实体类 `User` 86 | - 映射的表名称为 `user` 87 | - 字段 `id`(主键)、`username` 分别为 `int`、`string` 类型 -------------------------------------------------------------------------------- /docs/数据库/Mysql/安装Mysql.md: -------------------------------------------------------------------------------- 1 | ## 安装 MySQL 2 | 3 | 笔者认为`Windows` 并不适合安装 `MySQL5.7` :smile: 4 | 5 | 推荐 `MySQL8.0` 6 | 安装过程选择界面化安装即可,你甚至不必动用 `cmd` 命令来启动 `MySQL`,只需要把 `MYSQL` 加到 `Windows` 服务并设置开机即可,这里不在详述 7 | 8 | 9 | ### `Linux` 安装 `MySQL5.7` 10 | > 你知道,我总是愿意用 `Centos` 来代表 `Linux` 系统,我认为它是最好的 `Linux` 服务器 !! 11 | 12 | 13 | 14 | [MySQL 5.7 下载](https://cdn.mysql.com/Downloads/MySQL-5.7/mysql-5.7.29-1.el7.x86_64.rpm-bundle.tar) 15 | 16 | 17 | 进入下载目录解压文件 18 | ```shell 19 | tar -xvf mysql-5.7.29-1.el7.x86_64.rpm-bundle.tar 20 | ``` 21 | 22 | 查看原有 `MySQL` 版本,避免 `MySQL` 残留,如果是第一次安装直接忽略即可。 23 | ```shell 24 | rpm -qa|grep mariadb 25 | ``` 26 | 删除`MySQL` 残留,不存在则不用管它。 27 | ```shell 28 | rpm -e --nodeps mariadb-libs-5.5.60-1.el7_5.x86_64 29 | ``` 30 | 31 | 赋予该目录可执行权限,建议 `644` 即可,别一上来就 `777`,这很危险。 32 | ```shell 33 | chmod -R 644 mysql 34 | ``` 35 | 36 | 37 | 38 | `rpm` 安装 `MySQL` 依赖包 39 | 40 | ```shell 41 | rpm -ivh mysql-community-common-5.7.29-1.el7.x86_64.rpm 42 | rpm -ivh mysql-community-libs-5.7.29-1.el7.x86_64.rpm 43 | rpm -ivh mysql-community-client-5.7.29-1.el7.x86_64.rpm 44 | rpm -ivh mysql-community-server-5.7.29-1.el7.x86_64.rpm 45 | ``` 46 | 47 | 如果安装过程中报了 `GPG keys` 的错误 48 | 49 | 请在命令后面加上 `--force --nodeps` 参数,重新安装即可。 50 | 51 | 52 | ### `MySQL` 配置文件 53 | 54 | 进入`/etc/my.cnf`并加上如下语句 55 | 56 | ```shell 57 | skip-grant-tables 58 | character_set_server=utf8 59 | init_connect='SET NAMES utf8' 60 | ``` 61 | 62 | `注意第一行配置了免密登陆,配置成功后记得注释掉再重启 MySQL,否则将后悔莫及` 63 | 64 | `注意第一行配置了免密登陆,配置成功后记得注释掉再重启 MySQL,否则将后悔莫及` 65 | 66 | `注意第一行配置了免密登陆,配置成功后记得注释掉再重启 MySQL,否则将后悔莫及` 67 | 68 | 69 | 70 | 启动`MySQL`服务 71 | 72 | ```shell 73 | $ systemctl start mysqld.service 74 | ``` 75 | 76 | 此时黑窗口键入 `mysql` 可成功进入 `mysql` 客户端 77 | 78 | 设置宿主机密码,注意是本地密码而非远程连接密码 79 | ```shell 80 | update mysql.user set authentication_string=password('123456') where user='root'; 81 | flush privileges; 82 | ``` 83 | 84 | 推出 `mysql` 客户端并 `stop` 服务 85 | 86 | ```shell 87 | $ systemctl stop mysqld.service 88 | ``` 89 | 90 | 回到 `/etc/my.cnf` 配置文注释掉免密登陆的那一行,并重新启动 `mysql` 服务 91 | 92 | ```shell 93 | $ systemctl start mysqld.service 94 | ``` 95 | 96 | 发现再次进入客户端需要指定用户名密码登陆,也就是我们上面设置的用户密码了。 97 | 98 | 当然了,`123456` 这个密码连你看起来都有些不太好,`mysql` 在密码设置方面做了一些限制,一些不符合预期的密码不会被通过 !! 99 | 100 | 如果你执意使用简单(弱密码)的话,请做下全局设置: 101 | 102 | ```shell 103 | set global validate_password_policy=LOW; 104 | set global validate_password_length=4; 105 | flush privileges; 106 | ``` 107 | 108 | ### 暴露 `3306` 端口 109 | 110 | ```shell 111 | firewall-cmd --zone=public --add-port=3306/tcp --permanent 112 | firewall-cmd --reload 113 | ``` 114 | 115 | 如果你使用的是一些云服务商的服务器(比如阿里云、腾讯云),请登陆控制台设置开放 `3306` 端口即可 116 | 117 | ### 允许远程登陆 118 | 我们并不会每次都登陆云服务器去进行数据库操作,这就需要允许其他机器远程登陆宿主机。 119 | 120 | ```shell 121 | grant all privileges on *.* to 'root'@'%' identified by '654321' with grant option; 122 | flush privileges; 123 | ``` 124 | 125 | > 注意这里的密码有别于前面设置的宿主机本地登陆的密码。 126 | 127 | > 至此,`MySQL` 部署完毕 !! 128 | 129 | 130 | ### 卸载Mysql 131 | 132 | 133 | 134 | #### 1.查看mysql安装 135 | 136 | ```shell 137 | rpm -qa|grep -i mysql 138 | ``` 139 | 140 | #### 2.卸载前关闭mysql服务 141 | 142 | ```shell 143 | rpm -ev --nodeps mysql-community-release-el7-5.noarch 144 | rpm -ev --nodeps mysql-community-common-5.6.38-2.el7.x86_64 145 | rpm -ev --nodeps mysql-community-client-5.6.38-2.el7.x86_64 146 | rpm -ev --nodeps mysql-community-libs-5.6.38-2.el7.x86_64 147 | rpm -ev --nodeps community-server-5.6.38-2.el7.x86_64 148 | ``` 149 | 150 | 执行完命令之后再次执行 `rpm-qa|grep -i mysql` 会发现已经卸载完成。 151 | 152 | 153 | 接着执行命令 `find / -name mysql` 154 | 155 | 把查找出的目录删除 156 | ```shell 157 | rm -rf 上面查出的所有文件夹 158 | ``` 159 | 160 | 161 | > `etc/my.cnf` 如果存在的话手动删除,这样`mysql`就卸载完成了。 -------------------------------------------------------------------------------- /docs/数据库/Mysql/底层结构.md: -------------------------------------------------------------------------------- 1 | 2 | `Innodb` 和 `Myisam` 引擎的实现 3 | 4 | `Mysql` 底层数据引擎以插件形式设计,最常见的是 `Innodb` 引擎和 `Myisam` 引擎,用户可以根据个人需求选择不同的引擎作为 `Mysql` 数据表的底层引擎。我们刚分析了,`B+树`作为 `Mysql` 的索引的数据结构非常合适,但是数据和索引到底怎么组织起来也是需要一番设计,设计理念的不同也导致了 `Innodb` 和 `Myisam` 的出现,各自呈现独特的性能。 5 | 6 | `MyISAM` 虽然数据查找性能极佳,但是不支持事务处理。`Innodb` 最大的特色就是支持了 `ACID` 兼容的事务功能,而且他支持行级锁。`Mysql` 建立表的时候就可以指定引擎,比如下面的例子,就是分别指定了 `Myisam` 和 `Innodb` 7 | 8 | 9 | 执行这两个指令后,系统出现了以下的文件,说明这两个引擎数据和索引的组织方式是不一样的。 10 | 11 | `Innodb` 创建表后生成的文件有: 12 | 13 | - `frm`:创建表的语句 14 | - `idb`:表里面的数据+索引文件 15 | - `Myisam` 创建表后生成的文件有 16 | 17 | `MyISAM` 创建表后生成的文件有: 18 | - `frm`:创建表的语句 19 | - `MYD`:表里面的数据文件(myisam data) 20 | - `MYI`:表里面的索引文件(myisam index) 21 | 22 | ![](https://pic2.zhimg.com/80/v2-8a065d6e21a2adbf06d2e6b5dd02e969_1440w.jpg) 23 | 24 | 从生成的文件看来,这两个引擎底层数据和索引的组织方式并不一样,`MyISAM` 引擎把数据和索引分开了,一人一个文件,这叫做非聚集索引方式;`Innodb` 引擎把数据和索引放在同一个文件里了,这叫做`聚集索引`方式。下面将从底层实现角度分析这两个引擎是怎么依靠 `B+树`这个数据结构来组织引擎实现的。 25 | 26 | `MyISAM` 引擎的底层实现(`非聚集索引`方式) 27 | `MyISAM` 用的是非聚集索引方式,即数据和索引落在不同的两个文件上。`MyISAM` 在建表时以`主键`作为 KEY 来建立主索引 `B+树`,树的叶子节点存的是对应数据的物理地址。我们拿到这个物理地址后,就可以到 `MyISAM` 数据文件中直接定位到具体的数据记录了。 28 | 29 | ![](https://pic1.zhimg.com/80/v2-d9a03627e8e1319e46f42e6963c35e30_1440w.jpg) 30 | 31 | 32 | 当我们为某个字段添加索引时,我们同样会生成对应字段的索引树,该字段的索引树的叶子节点同样是记录了对应数据的物理地址,然后也是拿着这个物理地址去数据文件里定位到具体的数据记录。 33 | 34 | `Innodb` 引擎的底层实现(`聚集索引`方式) 35 | `InnoDB` 是聚集索引方式,因此数据和索引都存储在同一个文件里。首先 `Inno`DB 会根据`主键` ID 作为 KEY 建立索引 `B+树`,如左下图所示,而 `B+树`的叶子节点存储的是主键 ID 对应的数据, 36 | 37 | 比如在执行 `select * from user_info where id=15 `这个语句时,`InnoDB` 就会查询这颗`主键` ID 索引 `B+树`,找到对应的 `user_name='Bob'`。 38 | 39 | 这是建表的时候 `InnoDB` 就会自动建立好`主键` ID 索引树,这也是为什么 `Mysql` 在建表时要求必须指定主键的原因。当我们为表里某个字段加索引时 `InnoDB` 会怎么建立索引树呢?比如我们要给 `user_name` 这个字段加索引,那么 `InnoDB` 就会建立 `user_name` 索引` B+树`,节点里存的是 `user_name` 这个 KEY,叶子节点存储的数据的是主键 KEY。注意,叶子存储的是主键 KEY!拿到主键 KEY 后,`InnoDB` 才会去主键索引树里根据刚在 `user_name` 索引树找到的`主键` KEY 查找到对应的数据。 40 | 41 | ![](https://pic2.zhimg.com/80/v2-6e16b355e3d0f05ed8bfb0f7c71de8f1_1440w.jpg) 42 | 43 | 44 | 问题来了,为什么 `InnoDB` 只在主键索引树的叶子节点存储了具体数据,但是其他索引树却不存具体数据呢,而要多此一举先找到主键,再在主键索引树找到对应的数据呢? 45 | 46 | 其实很简单,因为 `InnoDB` 需要节省存储空间。一个表里可能有很多个索引,`InnoDB` 都会给每个加了索引的字段生成索引树,如果每个字段的索引树都存储了具体数据,那么这个表的索引数据文件就变得非常巨大(数据`极度冗余`了)。从节约磁盘空间的角度来说,真的没有必要每个字段索引树都存具体数据,通过这种看似“多此一举”的步骤,在牺牲较少查询的性能下节省了巨大的磁盘空间,这是非常有值得的。 47 | 48 | 在进行 `InnoDB` 和 `MyISAM` 特点对比时谈到,`MyISAM` 查询性能更好,从上面索引文件数据文件的设计来看也可以看出原因:MyISAM 直接找到物理地址后就可以直接定位到数据记录,但是 `InnoDB` 查询到叶子节点后,还需要再查询一次主键索引树,才可以定位到具体数据。等于 `MyISAM` 一步就查到了数据,但是 `InnoDB` 要两步,那当然 `MyISAM` 查询性能更高。 49 | 50 | 本文首先探讨了哪种数据结构更适合作为 `Mysql` 底层索引的实现,然后再介绍了 `Mysql` 两种经典数据引擎 `MyISAM` 和 `InnoDB` 的底层实现。最后再总结一下什么时候需要给你的表里的字段加索引吧: 51 | 52 | - 较频繁的作为查询条件的字段应该创建索引; 53 | - 唯一性太差的字段不适合单独创建索引,即使该字段频繁作为查询条件; 54 | - 更新非常频繁的字段不适合创建索引 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | ### 存储引擎比较 64 | 65 | 四.四种存储引擎比较 66 | 67 | `InnoDB`:支持事务处理,支持外键,支持崩溃修复能力和并发控制。如果需要对事务的完整性要求比较高(比如银行),要求实现并发控制(比如售票),那选择InnoDB有很大的优势。如果需要频繁的更新、删除操作的数据库,也可以选择InnoDB,因为支持事务的提交(commit)和回滚(rollback)。 68 | 69 | `MyISAM`:插入数据快,空间和内存使用比较低。如果表主要是用于插入新记录和读出记录,那么选择MyISAM能实现处理高效率。如果应用的完整性、并发性要求比 较低,也可以使用。如果数据表主要用来插入和查询记录,则MyISAM引擎能提供较高的处理效率 70 | 71 | `MEMORY`:所有的数据都在内存中,数据的处理速度快,但是安全性不高。如果需要很快的读写速度,对数据的安全性要求较低,可以选择MEMOEY。它对表的大小有要求,不能建立太大的表。所以,这类数据库只使用在相对较小的数据库表。如果只是临时存放数据,数据量不大,并且不需要较高的数据安全性,可以选择将数据保存在内存中的Memory引擎,MySQL中使用该引擎作为临时表,存放查询的中间结果 72 | 73 | `Archive`:如果只有INSERT和SELECT操作,可以选择`Archive`,Archive支持高并发的插入操作,但是本身不是事务安全的。Archive非常适合存储归档数据,如记录日志信息可以使用Archiv 74 | 75 | 注意,同一个数据库也可以使用多种存储引擎的表。如果一个表要求比较高的事务处理,可以选择InnoDB。这个数据库中可以将查询要求比较高的表选择MyISAM存储。如果该数据库需要一个用于查询的临时表,可以选择MEMORY存储引擎。 76 | 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /docs/数据库/Mysql/笑谈索引.md: -------------------------------------------------------------------------------- 1 | ### `创建索引的优势` 2 | 3 | - 提高数据的检索速度,降低数据库IO成本:使用索引的意义就是通过缩小表中需要查询的记录的数目从而加快搜索的速度。 4 | - 降低数据排序的成本,降低CPU消耗:索引之所以查的快,是因为先将数据排好序,若该字段正好需要排序,则真好降低了排序的成本。 5 | 6 | 7 | 8 | ### `创建索引的劣势` 9 | - 占用存储空间:索引实际上也是一张表,记录了主键与索引字段,一般以索引文件的形式存储在磁盘上。 10 | - 降低更新表的速度:表的数据发生了变化,对应的索引也需要一起变更,从而减低的更新速度。否则索引指向的物理数据可能不对,这也是索引失效的原因之一。 11 | - 优质索引创建难:索引的创建并非一日之功,也并非一直不变。需要频繁根据用户的行为和具体的业务逻辑去创建最佳的索引。 12 | 13 | 14 | 15 | ### `索引分类` 16 | 17 | 我们常说的索引一般指的是BTree(多路搜索树)结构组织的索引。其中还有 18 | `聚合索引`,`次要索引`,`复合索引`,`前缀索引`,`唯一索引`,统称`索引`,当然除了B+树外,还有哈希索引(hash index)等。 19 | 20 | 21 | 22 | - 单值索引:一个索引只包含单个列,一个表可以有多个单列索引 23 | - 唯一索引:索引列的值必须唯一,但允许有空值 24 | - 复合索引:一个索引包含多个列,实际开发中推荐使用 25 | 26 | > 实际开发中推荐使用复合索引,并且单表创建的索引个数建议不要超过五个 27 | 28 | 29 | ### `基本语法` 30 | 31 | ```shell 32 | create [unique] index indexName on tableName (columnName...) 33 | alter tableName add [unique] index [indexName] on (columnName...) 34 | 35 | drop index [indexName] on tableName 36 | 37 | show index from tableName 38 | ``` 39 | 40 | 41 | 42 | ### `哪些情况需要建索引` 43 | - 主键,唯一索引 44 | - 经常用作查询条件的字段需要创建索引 45 | - 经常需要排序、分组和统计的字段需要建立索引 46 | - 查询中与其他表关联的字段,外键关系建立索引 47 | 48 | 49 | 50 | ### `哪些情况不要建索引` 51 | - 表的记录太少,百万级以下的数据不需要创建索引 52 | - 经常增删改的表不需要创建索引 53 | - 数据重复且分布平均的字段不需要创建索引,如 true,false 之类。 54 | - 频发更新的字段不适合创建索引 55 | - where条件里用不到的字段不需要创建索引 56 | 57 | 58 | 59 | ### `性能分析` 60 | 61 | #### `MySQL` 自身瓶颈 62 | 63 | `MySQL`自身参见的性能问题有磁盘空间不足,磁盘I/O太大,服务器硬件性能低。 64 | - `CPU`:CPU 在饱和的时候一般发生在数据装入内存或从磁盘上读取数据时候 65 | - `IO`:磁盘I/O 瓶颈发生在装入数据远大于内存容量的时候 66 | - 服务器硬件的性能瓶颈:top,free,iostat 和 vmstat来查看系统的性能状态 67 | 68 | 69 | 70 | ### `explain` 分析`sql`语句 71 | 72 | 使用`explain`关键字可以模拟优化器执行sql查询语句,从而得知MySQL 是如何处理sql语句。 73 | 74 | 75 | 76 | ```shell 77 | +----+-------------+-------+------------+------+---------------+-----+---------+------+------+----------+-------+ 78 | | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | 79 | +----+-------------+-------+------------+------+---------------+-----+---------+------+------+----------+-------+ 80 | ``` 81 | 82 | 83 | 84 | 85 | 86 | #### `id` 87 | 88 | `select` 查询的序列号,包含一组可以重复的数字,表示查询中执行sql语句的顺序。一般有三种情况: 89 | - `id`全部相同,sql的执行顺序是由上至下; 90 | - `id`全部不同,sql的执行顺序是根据id大的优先执行; 91 | - `id`既存在相同,又存在不同的。先根据id大的优先执行,再根据相同id从上至下的执行。 92 | 93 | 94 | 95 | #### `select_type` 96 | 97 | `select` 查询的类型,主要是用于区别普通查询,联合查询,嵌套的复杂查询 98 | `simple`:简单的select 查询,查询中不包含子查询或者union 99 | `primary`:查询中若包含任何复杂的子查询,最外层查询则被标记为primary 100 | `subquery`:在select或where 列表中包含了子查询 101 | `derived`:在from列表中包含的子查询被标记为derived(衍生)MySQL会递归执行这些子查询,把结果放在临时表里。 102 | `union`:若第二个select出现在union之后,则被标记为union,若union包含在from子句的子查询中,外层select将被标记为:derived 103 | `union result`:从union表获取结果的select 104 | 105 | 106 | 107 | #### `partitions` 108 | 109 | 表所使用的分区,如果要统计十年公司订单的金额,可以把数据分为十个区,每一年代表一个区。这样可以大大的提高查询效率。 110 | 111 | 112 | #### `type` 113 | 114 | 这是一个非常重要的参数,连接类型,常见的有:`all` , `index` , `range` , `ref` , `eq_ref` , `const` , `system` , `null` 八个级别。性能从最优到最差的排序:`system` > `const` > `eq_ref` > `ref` > `range` > `index` > `all`。 115 | 116 | 117 | 118 | 对程序员来说,若保证查询至少达到`range`级别或者最好能达到ref则算是一个优秀而又负责的程序员。 119 | 120 | 121 | - `all`:(full table scan)全表扫描无疑是最差,若是百万千万级数据量,全表扫描会非常慢。 122 | - `index`:(full index scan)全索引文件扫描比all好很多,毕竟从索引树中找数据,比从全表中找数据要快。 123 | - `range`:只检索给定范围的行,使用索引来匹配行。范围缩小了,当然比全表扫描和全索引文件扫描要快。sql语句中一般会有between,in,>,< 等查询。 124 | - `ref`:非唯一性索引扫描,本质上也是一种索引访问,返回所有匹配某个单独值的行。比如查询公司所有属于研发团队的同事,匹配的结果是多个并非唯一值。 125 | - `eq_ref`:唯一性索引扫描,对于每个索引键,表中有一条记录与之匹配。比如查询公司的CEO,匹配的结果只可能是一条记录, 126 | - `const`:表示通过索引一次就可以找到,const用于比较primary key 或者unique索引。因为只匹配一行数据,所以很快,若将主键至于where列表中,MySQL就能将该查询转换为一个常量。 127 | - `system`:表只有一条记录(等于系统表),这是const类型的特列,平时不会出现,了解即可 128 | 129 | 130 | 131 | #### `possible_keys` 132 | 133 | 显示查询语句可能用到的索引(一个或多个或为null),不一定被查询实际使用。仅供参考使用。 134 | 135 | 136 | #### `key` 137 | 138 | 显示查询语句实际使用的索引。若为null,则表示没有使用索引。 139 | 140 | 141 | #### `key_len` 142 | 143 | 显示索引中使用的字节数,可通过key_len计算查询中使用的索引长度。在不损失精确性的情况下索引长度越短越好。key_len 显示的值为索引字段的最可能长度,并非实际使用长度,即key_len是根据表定义计算而得,并不是通过表内检索出的。 144 | 145 | 146 | #### `ref` 147 | 显示索引的哪一列或常量被用于查找索引列上的值。 148 | 149 | 150 | #### `rows` 151 | 152 | 根据表统计信息及索引选用情况,大致估算出找到所需的记录所需要读取的行数,值越大越不好。 153 | 154 | 155 | #### `extra` 156 | 157 | - `Using filesort`:说明MySQL会对数据使用一个外部的索引排序,而不是按照表内的索引顺序进行读取。MySQL中无法利用索引完成的排序操作称为“文件排序” 。出现这个就要立刻优化sql。 158 | - `Using temporary`:使用了临时表保存中间结果,MySQL在对查询结果排序时使用临时表。常见于排序 order by 和 分组查询 group by。出现这个更要立刻优化sql。 159 | - `Using index`:表示相应的select 操作中使用了覆盖索引(Covering index),避免访问了表的数据行,效果不错!如果同时出现Using where,表明索引被用来执行索引键值的查找。如果没有同时出现Using where,表示索引用来读取数据而非执行查找动作。 160 | - `覆盖索引`(Covering Index) :也叫索引覆盖,就是select 的数据列只用从索引中就能够取得,不必读取数据行,MySQL可以利用索引返回select 列表中的字段,而不必根据索引再次读取数据文件。 161 | - `Using index condition`:在5.6版本后加入的新特性,优化器会在索引存在的情况下,通过符合RANGE范围的条数 和 总数的比例来选择是使用索引还是进行全表遍历。 162 | - `Using where`:表明使用了where 过滤。 163 | - `Using join buffer`:表明使用了连接缓存。 164 | - `impossible where`:where 语句的值总是false,不可用,不能用来获取任何元素。 165 | - `distinct`:优化distinct操作,在找到第一匹配的元组后即停止找同样值的动作。 166 | - `filtered` 一个百分比的值,和rows 列的值一起使用,可以估计出查询执行计划(QEP)中的前一个表的结果集,从而确定join操作的循环次数。小表驱动大表,减轻连接的次数。 167 | 168 | 169 | 170 | #### 通过`explain`的参数介绍,我们可以得知 171 | - 表的读取顺序(id) 172 | - 数据读取操作的操作类型(type) 173 | - 哪些索引被实际使用(key) 174 | - 表之间的引用(ref) 175 | - 每张表有多少行被优化器查询(rows) 176 | 177 | 178 | 179 | ### `性能下降的原因` 180 | 181 | 从程序员的角度 182 | - 查询语句写的不好 183 | - 没建索引,索引建的不合理或索引失效 184 | - 关联查询有太多的join 185 | 186 | 从服务器的角度 187 | - 服务器磁盘空间不足 188 | - 服务器调优配置参数设置不合理 189 | 190 | 191 | 192 | ### `总结` 193 | 194 | 1. 索引是排好序且快速查找的数据结构。其目的是为了提高查询的效率。 195 | 196 | 2. 创建索引后,查询数据变快,但更新数据变慢。 197 | 198 | 3. 性能下降的原因很可能是索引失效导致。 199 | 200 | 4. 索引创建的原则,经常查询的字段适合创建索引,频繁需要更新的数据不适合创建索引。 201 | 202 | 5. 索引字段频繁更新,或者表数据物理删除容易造成索引失效。 203 | 204 | 6. 擅用 `explain` 分析`sql`语句 205 | 206 | 7. 除了优化sql语句外,还可以优化表的设计。如尽量做成单表查询,减少表之间的关联。设计归档表等。 207 | 208 | 209 | > 感谢:[好好学java](https://mp.weixin.qq.com/s/Jtz_dMoKln1tuTQYEjsY-A) 210 | 211 | 212 | 213 | 214 | 215 | 216 | ### 基础 217 | 讲联合索引,一定要扯最左匹配!放心,我不扯有的没的,几句话懂个大概就行! 218 | 219 | ### 最左匹配 220 | 221 | 所谓最左原则指的就是如果你的 SQL 语句中用到了联合索引中的最左边的索引,那么这条 SQL 语句就可以利用这个联合索引去进行匹配,值得注意的是,当遇到范围查询(>、<、between、like)就会停止匹配。 222 | 223 | 假设,我们对(a,b)字段建立一个索引,也就是说,你where后条件为 224 | 225 | `a = 1 226 | a = 1 and b = 2` 227 | 是可以匹配索引的。但是要注意的是~你执行 228 | 229 | `b= 2 and a =1` 230 | 也是能匹配到索引的,因为Mysql有优化器会自动调整a,b的顺序与索引顺序一致。 相反的,你执行 231 | `b = 2` 232 | 就匹配不到索引了。 而你对`(a,b,c,d)`建立索引,where后条件为 233 | `a = 1 and b = 2 and c > 3 and d = 4` 234 | 235 | 那么,a,b,c三个字段能用到索引,而d就匹配不到。因为遇到了范围查询! 236 | 237 | 最左匹配的原理? 238 | 239 | 假设,我们对(a,b)字段建立索引,那么入下图所示 240 | 241 | ![](https://pic2.zhimg.com/80/v2-fcde0ef783885b6b17999f39ca2808b5_1440w.jpg) 242 | 243 | 244 | 如图所示他们是按照a来进行排序,在a相等的情况下,才按b来排序。 245 | 246 | 因此,我们可以看到a是有序的1,1,2,2,3,3。而b是一种全局无序,局部相对有序状态! 什么意思呢? 247 | 248 | 从全局来看,b的值为1,2,1,4,1,2,是无序的,因此直接执行b = 2这种查询条件没有办法利用索引。 249 | 250 | 从局部来看,当a的值确定的时候,b是有序的。例如a = 1时,b值为1,2是有序的状态。当a=2时候,b的值为1,4也是有序状态。 因此,你执行a = 1 and b = 2是a,b字段能用到索引的。而你执行a > 1 and b = 2时,a字段能用到索引,b字段用不到索引。因为a的值此时是一个范围,不是固定的,`在这个范围内b值不是有序的,因此b字段用不上索引`。 251 | 252 | 综上所示,`最左匹配原则`,`在遇到范围查询的时候,就会停止匹配`。 253 | 254 | 实战 255 | OK,懂上面的基础,我们就可以开始扯了~我举了经典的五大题型,看完基本就懂! 256 | 257 | 题型一 258 | 如果sql为 259 | 260 | `SELECT * FROM table WHERE a = 1 and b = 2 and c = 3;` 261 | 262 | 如何建立索引? 263 | 264 | 如果此题回答为对(a,b,c)建立索引,那都可以回去等通知了。 此题正确答法是,(a,b,c)或者(c,b,a)或者(b,a,c)都可以,重点要的是将区分度高的字段放在前面,区分度低的字段放后面。像性别、状态这种字段区分度就很低,我们一般放后面。 265 | 266 | 例如假设区分度由大到小为b,a,c。那么我们就对(b,a,c)建立索引。在执行sql的时候,优化器会 帮我们调整where后a,b,c的顺序,让我们用上索引。 267 | 268 | 题型二 269 | 如果sql为 270 | 271 | `SELECT * FROM table WHERE a > 1 and b = 2;` 272 | 273 | 如何建立索引? 274 | 275 | 如果此题回答为对(a,b)建立索引,那都可以回去等通知了。 此题正确答法是,对(b,a)建立索引。如果你建立的是(a,b)索引,那么只有a字段能用得上索引,毕竟最左匹配原则遇到范围查询就停止匹配。 如果对(b,a)建立索引那么两个字段都能用上,优化器会帮我们调整where后a,b的顺序,让我们用上索引。 276 | 277 | 题型三 278 | 如果sql为 279 | 280 | `SELECT * FROM `table` WHERE a > 1 and b = 2 and c > 3;` 281 | 282 | 如何建立索引? 此题回答也是不一定,(b,a)或者(b,c)都可以,要结合具体情况具体分析。 283 | 284 | 拓展一下 285 | 286 | `SELECT * FROM `table` WHERE a = 1 and b = 2 and c > 3;` 287 | 288 | 怎么建索引?嗯,大家一定都懂了! 289 | 290 | 题型四 291 | `SELECT * FROM `table` WHERE a = 1 ORDER BY b;` 292 | 293 | 如何建立索引? 这还需要想?一看就是对(a,b)建索引,当a = 1的时候,b相对有序,可以避免再次排序! 那么 294 | 295 | `SELECT * FROM `table` WHERE a > 1 ORDER BY b;` 296 | 297 | 如何建立索引? 对(a)建立索引,因为a的值是一个范围,这个范围内b值是无序的,没有必要对(a,b)建立索引。 298 | 299 | 拓展一下 300 | 301 | `SELECT * FROM `table` WHERE a = 1 AND b = 2 AND c > 3 ORDER BY c;` 302 | 303 | 怎么建索引? 304 | 305 | 题型五 306 | `SELECT * FROM `table` WHERE a IN (1,2,3) and b > 1;` 307 | 308 | 如何建立索引? 309 | 310 | 还是对(a,b)建立索引,因为IN在这里可以视为等值引用,不会中止索引匹配,所以还是(a,b)! 311 | 312 | 拓展一下 313 | 314 | `SELECT * FROM `table` WHERE a = 1 AND b IN (1,2,3) AND c > 3 ORDER BY c;` 315 | 316 | 如何建立索引?此时c排序是用不到索引的。 317 | 318 | 319 | 320 | > 再次参考傲丙的文章 [我以为我对数据库索引很了解?](https://zhuanlan.zhihu.com/p/107487215) 321 | 322 | 323 | -------------------------------------------------------------------------------- /docs/数据库/Redis/Redis.md: -------------------------------------------------------------------------------- 1 | ## Redis 队列 2 | 3 | ```shell 4 | pip install redis 5 | ``` 6 | 7 | ```python 8 | # coding=utf-8 9 | import redis 10 | 11 | class RedisQueue(object): 12 | 13 | def __init__(self, **redis_kwargs): 14 | self.__db = redis.Redis(host='127.0.0.1', port=6379, db=6, password=None) 15 | 16 | # 返回队列大小 17 | def qsize(self, name): 18 | return self.__db.llen(name) 19 | 20 | # 判断队列用尽 21 | def empty(self,name): 22 | return self.qsize(name) == 0 23 | 24 | # rpush进去或者lpush都可以 25 | def put(self, name, item): 26 | self.__db.rpush(name, item) 27 | 28 | def sadd(self, name, v_): 29 | self.__db.sadd(name, v_) 30 | 31 | def spop(self, name): 32 | return self.__db.spop(name) 33 | 34 | def get_set_count(self, name): 35 | return self.__db.scard(name) 36 | 37 | def set_empty(self, name): 38 | return self.get_set_count(name) == 0 39 | 40 | # get出来 41 | def get(self, name, block=True, timeout=5): 42 | if block: 43 | item = self.__db.blpop(name, timeout=timeout) 44 | else: 45 | item = self.__db.lpop(name) 46 | return item 47 | 48 | # 直接lpop() 49 | def get_nowait(self): 50 | return self.get(False) 51 | 52 | # if __name__ == '__main__': 53 | # R = RedisQueue() 54 | # R.sadd() 55 | 56 | ``` 57 | 58 | -------------------------------------------------------------------------------- /docs/数据库/Redis/安装Redis.md: -------------------------------------------------------------------------------- 1 | ### 安装`gcc`依赖 2 | 3 | 由于 `redis` 是用 C 语言开发,安装之前必先确认是否安装 `gcc` 环境(`gcc -v`), 4 | 如果没有安装,执行以下命令进行安装 5 | 6 | ```shell 7 | yum install -y gcc 8 | ``` 9 | 10 | ### 下载并解压安装包 11 | 这里以 `/usr/local/redis-5.0.3` 目录为例 12 | ```shell 13 | wget http://download.redis.io/releases/redis-5.0.3.tar.gz 14 | tar -zxvf redis-5.0.3.tar.gz 15 | ``` 16 | 17 | ### 解压目录、编译、安装 18 | 19 | 编译安装到 `/usr/local/redis` 20 | ```shell 21 | cd redis-5.0.3 22 | make 23 | make install PREFIX=/usr/local/redis 24 | ``` 25 | 26 | 27 | 28 | ### 启动服务 29 | ```shell 30 | cd /usr/local/redis/bin/ 31 | ./redis-server 32 | ``` 33 | 34 | ### 后台启动 35 | 36 | 从 `redis` 的源码目录中复制 `redis.conf` 到 `redis` 的安装目录 37 | ```shell 38 | cp /usr/local/redis-5.0.3/redis.conf /usr/local/redis/bin/ 39 | ``` 40 | 41 | ```shell 42 | vi redis.conf 43 | ``` 44 | 修改 `redis.conf` 文件,把 `daemonize no` 改为 `daemonize yes` (后台驻留) 45 | 46 | 搜索 `requirepass` 并修改密码项 `requirepass 你的密码` 47 | 48 | 49 | ### 配置文件启动 50 | ```shell 51 | ./redis-server ./redis.conf 52 | ``` 53 | 54 | 现在就可以`后台启动`并开启`远程连接`了,连接密码就是配置文件中设置的 `requirepass` 55 | 56 | > 需要注意的是 `config set requirepass test123` 在`配置文件`启动时并`不会生效`,二者并无关联。 57 | 58 | -------------------------------------------------------------------------------- /docs/数据库/readme.md: -------------------------------------------------------------------------------- 1 | ### 关于数据库 2 | 3 | `数据库`通常用于后端开发的数据存储以及搭建`大数据仓库`, 4 | 但这种理念在大数据和智能化办公时代正在被打破,现在越来越多的非开发工作者都开始使用数据库存储日常数据。 5 | 6 | 而不总是游离于`Excel`操作层面(当然`excel`是一个很好的跨时代产物),从而实现了在可视化表格和数据库之间的完美 7 | 交互。 8 | 9 | 不仅为日常数据提供的`持久化`储存,同时在数据查询和大批量操作等方面也都将得到优化,数据库储存不再是开发者的专利。 10 | 11 | 12 | ### 使用场景 13 | 14 | [comment]: <> (当然了,对于一些没有必要做持久化存储的数据,采用表格文件存储也是一种很常见的手段,毕竟对于非开发和运维人员在日常工作中很难离开excel 表格,) 15 | [comment]: <> (但我更看重数据库和本地表格的强交互,这能让你更具效率 !!) 16 | 17 | #### `MongoDB`适合做什么 18 | `MongoDB`适合储存大量关联性不强的数据。 19 | `MongoDB`中的数据以"库"—"集合"—"文档"—"字段"结构进行储存。 20 | 21 | 这种结构咋看和传统关系型数据库的"库"—"表"—"行"—"列"结构非常像。 22 | 23 | 但是,`MongoDB`不需要预先定义表结构,数据的字段可以任意变动,并发写入速度也远远超过传统关系型数据库。 24 | 25 | #### `Redis`适合做什么 26 | `Redis`有多种数据结构,适合多种不同的应用场景。 27 | 28 | 1.使用`Redis`做缓存。`Redis`的字符串、哈希表两种数据结构适合用来储存大量的键值对信息,从而实现高速缓存。 29 | 30 | 2.使用`Redis`做队列。 31 | `Redis`有多几种数据结构适于做队列: 32 | - 使用`列表数据`结构,可以实现普通级和优先级队列的功能。 33 | - 使用`有序集合`数据结构,可以实现优先级队列; 34 | - 使用`哈希表数据`结构,可以实现延时队列。 35 | 36 | 3.使用`Redis`去重。 37 | `Redis`有多几种数据结构适于做去重: 38 | - 利用`集合`数据结构,可以实现小批量数据的去重; 39 | - 利用`字符串`数据结构的位操作,可以实现`布隆过滤器`,从而实现超大规模的数据去重; 40 | - 利用`Redis`自带的`HyperLogLog`数据结构,可以实现超大规模数据的去重和计数。 41 | 42 | 4.使用`Redis`实现积分板。 43 | `Redis`的`有序集合`功能可以实现积分板功能,还能实现自动排序、排名功能。 44 | 45 | 5.使用`Redis`实现`发布/订阅`功能。 46 | `Redis`自带的`发布/订阅`模式可以实现多对多的`发布/订阅`功能。 47 | 48 | #### `MongoDB`与`Redis`在多个领域中都有重要的应用。 49 | 50 | - 在爬虫开发中,`MongoDB`主要用来写数据,`Redis`主要用来缓存网址。 51 | - 在数据分析中,`MongoDB`的聚合操作用得较多。 52 | - 在后端开发中,主要用到`MongoDB`的增、删、改、查功能,`Redis`主要用来做缓存。 53 | - 在游戏开发中,`Redis`可以用来做排名功能。 54 | 55 | > 如果希望更好地掌握`MongoDB`和`Redis`, 56 | 那么可以在多个领域都寻找项目来进行尝试,从而更全面地了解各个功能和应用场景。 57 | 58 | > 摘自青南大佬 《左手MongoDB,右手Redis》 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /docs/杂谈/可视化工具及安装.md: -------------------------------------------------------------------------------- 1 | 2 | 前几天刚换了个 `deepin` 系统,目前没发现什么大问题,以后可能会在 `deepin` 和 `ubuntu` 下进行 `Python/go` 开发。 3 | 4 | 凌乱繁多的工具和命令对我的记忆是一个折磨,我必须要找个地方记录这些东西。 5 | 6 | 7 | ## `AnotherRedisDesktopManager` 8 | 9 | `redis`数据库可视化工具。 10 | 11 | 如果是 `Mac` 或 `Windows` 系统请继续选用 `RedisDesktopManager` 即可,`RedisDesktopManager` 对不同的`linux`发行版不适用, 12 | 笔者在适用 `deepin` 系统时不慎掉坑,最后发现了 `AnotherRedisDesktopManager` 并沿用了它。 13 | 14 | 15 | > 国内`Gitee`:[`AnotherRedisDesktopManager`](https://gitee.com/qishibo/AnotherRedisDesktopManager/releases) 16 | 17 | 18 | 根据后缀名对应的系统下载安装即可 19 | 20 | - `Another-Redis-Desktop-Manager.1.4.5.AppImage` Linux 系统 21 | - `Another-Redis-Desktop-Manager.1.4.5.dmg` Mac系统 22 | - `Another-Redis-Desktop-Manager.1.4.5.exe` Windows系统 23 | 24 | 安装后双击打开即可看到页面。 25 | 26 | 27 | 28 | ## `datagrip` 29 | 30 | 官网下载: [https://www.jetbrains.com/datagrip/download/#section=linux](https://www.jetbrains.com/datagrip/download/#section=linux) 31 | 32 | 默认下载只有 `30` 天的免费使用,我们用插件进行破解,让其无限使用。 33 | 34 | - 下载插件(`zip文件`),拖拽到 `datagrip` 编辑器 35 | - `auto reset`,通过每次自动重置的方法无限延长使用时间 36 | - 重启生效,实际上每次使用关闭下次启动时就重置了一次 37 | 38 | 这个插件文件我放在了`github` 上。 39 | 40 | 41 | ## `.sh` 制作桌面应用 42 | 43 | ```shell 44 | vim datagrip.desktop 45 | ``` 46 | 47 | 48 | ```shell 49 | [Desktop Entry] 50 | #快捷方式的名字 51 | Name=datagrip 52 | Name[zh_CN]=datagrip 53 | #显示图标 54 | Icon=/opt/datagrip/bin/datagrip.png 55 | #程序路径 56 | Exec=/opt/datagrip/bin/datagrip.sh 57 | StartupNotify=false 58 | Terminal=false 59 | Type=Application 60 | Categories=Application 61 | ``` 62 | 63 | 这样双击桌面应用就可以打开 `datagrip` 编辑器。 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /docs/杂谈/破解包/jetbrains无限重置插件.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PY-GZKY/PythonAutomatedOfficeGuide/014509edfeff065588a1391c5a3e116976a40531/docs/杂谈/破解包/jetbrains无限重置插件.zip -------------------------------------------------------------------------------- /docs/桌面自动化/readme.md: -------------------------------------------------------------------------------- 1 | 所谓的 `RPA` 桌面流程自动化,一般来说就是指`桌面自动化`(`表格`、`文档`、`各类应用`) + `网页自动化`。 2 | 3 | 现在很多的 `RPA` 软件或工具将网页自动化嵌入到了桌面自动化中,而对于我们将使用的 `Python` 语言来说, 4 | 则需要从独立的库和模块去编写自动化代码。这也就意味着 `Python` 编码将更加`灵活`、更易`扩展`。 5 | 6 | 事实上 `RPA` 软件的底层实现也是由`不同语言`、不同模块(比如表格上传下载、自动发送邮件、网页操作) 实现的`整合`,然后经过`封装`和`优化`,用`界面流程化`的方式呈现在我们眼前。 7 | 8 | -------------------------------------------------------------------------------- /docs/桌面自动化/快速初始化.md: -------------------------------------------------------------------------------- 1 | `桌面自动化` 的操作对象是`桌面应用`。 2 | 3 | 比如要让一些数据写入 `excel` 表格,首先需要连接 `office` 或 `wps office` 软件,激活窗口,然后再将数据写入指定表格,桌面自动化一切都是`可见的`,而诸如像 `pandas` 这类的库操作对象则是`表格本身`,要正确理解这层关系,才能更好的区分`桌面自动化`和`脚本`。 4 | 5 | 当然我们最终需要让它结合并发挥尽可能大的威力。这也就是很多的 `RPA` 软件既支持自动化流程(`编辑 6 | 拖拽式`),同时提供代码脚本的`编写`和`运行`,他们(`RPA`开发者) 正在最大程度上挖掘`自动化`所谓带来的威力。 7 | 8 | ## 一些库的介绍 9 | 10 | ### `pywinauto` 11 | ### `pyautogui` 12 | ### `pykeyboard` 13 | 14 | -------------------------------------------------------------------------------- /docs/浏览器自动化/pyppeteer/images/Chromium.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PY-GZKY/PythonAutomatedOfficeGuide/014509edfeff065588a1391c5a3e116976a40531/docs/浏览器自动化/pyppeteer/images/Chromium.png -------------------------------------------------------------------------------- /docs/浏览器自动化/pyppeteer/images/book_douban.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PY-GZKY/PythonAutomatedOfficeGuide/014509edfeff065588a1391c5a3e116976a40531/docs/浏览器自动化/pyppeteer/images/book_douban.png -------------------------------------------------------------------------------- /docs/浏览器自动化/pyppeteer/images/pyppeteer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PY-GZKY/PythonAutomatedOfficeGuide/014509edfeff065588a1391c5a3e116976a40531/docs/浏览器自动化/pyppeteer/images/pyppeteer.png -------------------------------------------------------------------------------- /docs/浏览器自动化/pyppeteer/images/异常浏览器检测.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PY-GZKY/PythonAutomatedOfficeGuide/014509edfeff065588a1391c5a3e116976a40531/docs/浏览器自动化/pyppeteer/images/异常浏览器检测.png -------------------------------------------------------------------------------- /docs/浏览器自动化/pyppeteer/images/正常浏览器检测.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PY-GZKY/PythonAutomatedOfficeGuide/014509edfeff065588a1391c5a3e116976a40531/docs/浏览器自动化/pyppeteer/images/正常浏览器检测.png -------------------------------------------------------------------------------- /docs/浏览器自动化/pyppeteer/images/浏览器版本.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PY-GZKY/PythonAutomatedOfficeGuide/014509edfeff065588a1391c5a3e116976a40531/docs/浏览器自动化/pyppeteer/images/浏览器版本.png -------------------------------------------------------------------------------- /docs/浏览器自动化/pyppeteer/images/浏览器驱动版本.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PY-GZKY/PythonAutomatedOfficeGuide/014509edfeff065588a1391c5a3e116976a40531/docs/浏览器自动化/pyppeteer/images/浏览器驱动版本.png -------------------------------------------------------------------------------- /docs/浏览器自动化/pyppeteer/images/绕过浏览器检测.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PY-GZKY/PythonAutomatedOfficeGuide/014509edfeff065588a1391c5a3e116976a40531/docs/浏览器自动化/pyppeteer/images/绕过浏览器检测.png -------------------------------------------------------------------------------- /docs/浏览器自动化/pyppeteer/images/豆瓣登陆成功.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PY-GZKY/PythonAutomatedOfficeGuide/014509edfeff065588a1391c5a3e116976a40531/docs/浏览器自动化/pyppeteer/images/豆瓣登陆成功.png -------------------------------------------------------------------------------- /docs/浏览器自动化/pyppeteer/一些案例.md: -------------------------------------------------------------------------------- 1 | ## 自动抓取豆瓣读书 2 | 3 | ## 抓取京东商品信息 4 | 5 | ## 下载网页文件 6 | 7 | ## 滑动验证码 -------------------------------------------------------------------------------- /docs/浏览器自动化/pyppeteer/初始化pyppeteer.md: -------------------------------------------------------------------------------- 1 | `Puppeteer` 是一款基于 `NodeJS` 的 `web` 浏览器测试工具 2 | 3 | `Pyppeteer` 是 `Puppeteer` 的 `Python` 实现,在用法上并无二至,可惜的是 `Pyppeteer` 如今已经无人维护。最近一次项目的更新时间还停留在 `2018年9月` 4 | 5 | 6 | > [GitHub 项目地址](https://github.com/miyakogi/pyppeteer) 7 | 8 | > [文档](https://miyakogi.github.io/pyppeteer) 9 | 10 | 这是一款基于异步实现的自动化框架,这也就意味 `Pyppeteer` 在性能上要比 `selenium` 要好,但是 `Pyppeteer` 是一个非官方版本, 11 | 在文档支持方面仍有欠缺,这给想入门web自动化的小伙伴造成了不小的困扰。 12 | 13 | 14 | ## `chromium` 15 | 16 | 总言之,`Chromium` 是一款独立的浏览器,你也可以认为它是 `chrome` 的开发版本,因为我们现在看到的 `chrome` 浏览器的功能都会先在 `Chromium` 上实现。 17 | 18 | `Chromium` 是完全开源的,非常适用于 `web` 测试开发, 19 | 只不过它从外观看起来和 `chrome` 略有不同(几种不同程度的蓝色构成) 20 | 21 | ![](./images/Chromium.png ':size=70%') 22 | 23 | 24 | ## 安装 `Pyppeteer` 25 | 26 | ```shell 27 | pip install pyppeteer 28 | ``` 29 | 30 | 首次通过 `pip` 安装 `pyppeteer` 时会自动下载安装 `Chromium` 浏览器,这也太热情了。 31 | 32 | 如果由于网络原因导致下载终端,可以 [手动下载](https://chromium.en.softonic.com/) 33 | 34 | 下载完成后通过 `Pyppeteer` 查看 `Chromium` 版本和所在路径 35 | 36 | ```python 37 | import pyppeteer 38 | print(pyppeteer.__chromium_revision__) # 查看版本号 39 | print(pyppeteer.executablePath()) # 查看 Chromium 路径 40 | ``` 41 | ```text 42 | 588429 43 | C:\Users\HP\AppData\Local\pyppeteer\pyppeteer\local-chromium\588429\chrome-win32\chrome.exe 44 | ``` 45 | > 如果在初始化 `pyppeteer` 对象的时候不指定浏览器路径,则会默认使用该路径打开 `chromium` 浏览器 46 | 47 | ## 测试 `Pyppeteer` 48 | ```python 49 | # -*- coding: utf-8 -*- 50 | import asyncio 51 | from pyppeteer import launch 52 | 53 | async def main(): 54 | browser = await launch(headless=False) # 有头浏览器 55 | page = await browser.newPage() # 打开一个新的标签页 56 | await page.goto('https://pypi.org/project/pyppeteer/') # 跳转 57 | await page.screenshot({'path': '../images/pyppeteer.png'}) # 截图 58 | await browser.close() # 关闭浏览器 59 | 60 | asyncio.get_event_loop().run_until_complete(main()) 61 | ``` 62 | 63 | 截图效果 64 | 65 | ![](./images/pyppeteer.png ':size=70%') 66 | 67 | > 看起来不错,和我们预想的完全一致 68 | 69 | > 这说明 `Pyppeteer` 已经能够正常启动并工作了 -------------------------------------------------------------------------------- /docs/浏览器自动化/pyppeteer/编写自动下载表格程序.md: -------------------------------------------------------------------------------- 1 | ## 自动化操作之批量下载表格(1.1) 2 | 3 | > `Python` 在爬虫和数据分析领域可谓是独当一面,但由于 Python 的第三方扩展太过强大,在自动化操作领域也是占据了一席之地。 4 | 5 | ### 了解需求 6 | 7 | 产品经理: 运营部门最近的干饭人太多了,你帮他们写个每日自动下载表格的自动化脚本 8 | 9 | 10 | 11 | ### 剖析需求 12 | 13 | 网页端下载表格,大概可以分为两种模式: 14 | - 采用自动化工具,Python 库来说当选 `Pyppeteer` 和 `Selenium` 进行模拟人工的点击,输入,保存一系列流程化的操作 15 | - 采用自动化工具和脚本结合的方式,首先用自动化工具(比如`Pyppeteer`)进行登陆操作,获取其 `cookie` 以及相关的参数,写个脚本传入必要参数下载表格到本地 16 | 17 | 自动下载流程是当我们点击导出按钮的时候,系统会生成一个下载任务ID,一般会使用 `uuid` 生成 18 | 19 | 如果数据较大,导出需要一定时间,系统会一直轮询是否导出完成,如果完成就加入已下载的任务列表中,我们需要去任务列表页面把文件下载下来。 20 | 21 | 如果文件大小超过某个值,下载下来的则是 `zip` 格式,否则为正常格式(`excel`) 22 | 23 | 我们需要根据这个流程来编写下载脚本 24 | 25 | 26 | ### 编写代码 27 | 28 | #### 自动化登陆代码 29 | 30 | ```python 31 | # -*- coding: utf-8 -*- 32 | import json 33 | import os 34 | import sys 35 | import asyncio 36 | import random 37 | import tkinter 38 | from pyppeteer.launcher import launch 39 | 40 | class Login(): 41 | 42 | def __init__(self, shopId): 43 | self._frame = None 44 | self.cnRedisQueue = RedisQueue("cookie") 45 | self.shopId = shopId 46 | self.loginUrl = LOGIN_URL 47 | 48 | def screen_size(self): 49 | tk = tkinter.Tk() 50 | width = tk.winfo_screenwidth() 51 | height = tk.winfo_screenheight() 52 | tk.quit() 53 | return {'width': width, 'height': height} 54 | 55 | async def login(self): 56 | self.browser = await launch( 57 | { 58 | 'headless': False, 59 | 'dumpio': True, 60 | 'ignoreDefaultArgs': ['--enable-automation'] 61 | }, 62 | args=['--user-data-dir=./userData'], 63 | ) 64 | page = await self.browser.newPage() 65 | await asyncio.sleep(2) 66 | 67 | try: 68 | await page.setViewport(viewport=self.screen_size()) 69 | await page.setJavaScriptEnabled(enabled=True) # 启用js 70 | await page.setUserAgent( 71 | 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36 Edge/16.16299' 72 | ) 73 | await self.page_evaluate(page) 74 | 75 | await page.goto(self.loginUrl) 76 | await page.waitFor(6000) 77 | await page.evaluate('document.querySelector("#fm-login-id").value=""') 78 | await page.type('#fm-login-id', USERNAME, {'delay': self.input_time_random() - 10}) # delay是限制输入的时间 79 | await page.evaluate('document.querySelector("#fm-login-password").value=""') 80 | await page.type('#fm-login-password', PASSWORD, {'delay': self.input_time_random() - 50}) 81 | await page.waitFor(6000) 82 | await page.click('.fm-submit') 83 | await page.waitFor(6000) 84 | 85 | await asyncio.sleep(2) 86 | await page.goto(self.loginUrl) 87 | await page.waitFor(12000) 88 | await self.get_cookie(page) 89 | await page.waitFor(12000) 90 | await self.page_close(self.browser) 91 | return {'code': 200, 'msg': '登陆成功'} 92 | except: 93 | return {'code': -1, 'msg': '出错'} 94 | finally: 95 | await page.waitFor(3000) 96 | await self.page_close(self.browser) 97 | 98 | async def get_cookie(self,page): 99 | cookies_list = await page.cookies() 100 | cookies = '' 101 | for cookie in cookies_list: 102 | str_cookie = '{0}={1}; ' 103 | str_cookie = str_cookie.format(cookie.get('name'), cookie.get('value')) 104 | cookies += str_cookie 105 | print(cookies) 106 | return cookies 107 | 108 | async def page_evaluate(self, page): 109 | await page.evaluate('''() =>{ Object.defineProperties(navigator,{ webdriver:{ get: () => undefined } }) }''') 110 | await page.evaluate('''() =>{ window.navigator.chrome = { runtime: {}, }; }''') 111 | await page.evaluate( 112 | '''() =>{ Object.defineProperty(navigator, 'languages', { get: () => ['en-US', 'en'] }); }''') 113 | await page.evaluate( 114 | '''() =>{ Object.defineProperty(navigator, 'plugins', { get: () => [1, 2, 3, 4, 5,6], }); }''') 115 | await page.waitFor(3000) 116 | 117 | async def page_close(self, browser): 118 | for _page in await browser.pages(): 119 | await _page.close() 120 | await browser.close() 121 | 122 | def input_time_random(self): 123 | return random.randint(100, 151) 124 | 125 | def run(self): 126 | loop = asyncio.get_event_loop() 127 | i_future = asyncio.ensure_future(self.login()) 128 | loop.run_until_complete(i_future) 129 | return i_future.result() 130 | 131 | 132 | if __name__ == '__main__': 133 | Smt = Login(shopId=None) 134 | Smt.run() 135 | 136 | ``` 137 | 138 | 139 | 上面就是利用 `Pyppeteer` 进行自动登陆的脚本了 140 | 141 | 不管是想通过登陆获取 `cookie` 还是采用 `Pyppeteer` 做完一些列的操作,都需要先完成这一步 142 | 143 | 144 | -------------------------------------------------------------------------------- /docs/浏览器自动化/pyppeteer/编写自动登录程序.md: -------------------------------------------------------------------------------- 1 | ## 自动化操作之批量下载表格(1.1) 2 | 3 | > `Python` 在爬虫和数据分析领域可谓是独当一面,但由于 Python 的第三方扩展太过强大,在自动化操作领域也是占据了一席之地。 4 | 5 | ### 了解需求 6 | 7 | 产品经理: 运营部门最近的干饭人太多了,你帮他们写个每日自动下载表格的自动化脚本 8 | 9 | 10 | 11 | ### 剖析需求 12 | 13 | 网页端下载表格,大概可以分为两种模式: 14 | - 采用自动化工具,Python 库来说当选 `Pyppeteer` 和 `Selenium` 进行模拟人工的点击,输入,保存一系列流程化的操作 15 | - 采用自动化工具和脚本结合的方式,首先用自动化工具(比如`Pyppeteer`)进行登陆操作,获取其 `cookie` 以及相关的参数,写个脚本传入必要参数下载表格到本地 16 | 17 | 自动下载流程是当我们点击导出按钮的时候,系统会生成一个下载任务ID,一般会使用 `uuid` 生成 18 | 19 | 如果数据较大,导出需要一定时间,系统会一直轮询是否导出完成,如果完成就加入已下载的任务列表中,我们需要去任务列表页面把文件下载下来。 20 | 21 | 如果文件大小超过某个值,下载下来的则是 `zip` 格式,否则为正常格式(`excel`) 22 | 23 | 我们需要根据这个流程来编写下载脚本 24 | 25 | 26 | ### 编写代码 27 | 28 | #### 自动化登陆代码 29 | 30 | ```python 31 | # -*- coding: utf-8 -*- 32 | import json 33 | import os 34 | import sys 35 | import asyncio 36 | import random 37 | import tkinter 38 | from pyppeteer.launcher import launch 39 | 40 | class Login(): 41 | 42 | def __init__(self, shopId): 43 | self._frame = None 44 | self.cnRedisQueue = RedisQueue("cookie") 45 | self.shopId = shopId 46 | self.loginUrl = LOGIN_URL 47 | 48 | def screen_size(self): 49 | tk = tkinter.Tk() 50 | width = tk.winfo_screenwidth() 51 | height = tk.winfo_screenheight() 52 | tk.quit() 53 | return {'width': width, 'height': height} 54 | 55 | async def login(self): 56 | self.browser = await launch( 57 | { 58 | 'headless': False, 59 | 'dumpio': True, 60 | 'ignoreDefaultArgs': ['--enable-automation'] 61 | }, 62 | args=['--user-data-dir=./userData'], 63 | ) 64 | page = await self.browser.newPage() 65 | await asyncio.sleep(2) 66 | 67 | try: 68 | await page.setViewport(viewport=self.screen_size()) 69 | await page.setJavaScriptEnabled(enabled=True) # 启用js 70 | await page.setUserAgent( 71 | 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36 Edge/16.16299' 72 | ) 73 | await self.page_evaluate(page) 74 | 75 | await page.goto(self.loginUrl) 76 | await page.waitFor(6000) 77 | await page.evaluate('document.querySelector("#fm-login-id").value=""') 78 | await page.type('#fm-login-id', USERNAME, {'delay': self.input_time_random() - 10}) # delay是限制输入的时间 79 | await page.evaluate('document.querySelector("#fm-login-password").value=""') 80 | await page.type('#fm-login-password', PASSWORD, {'delay': self.input_time_random() - 50}) 81 | await page.waitFor(6000) 82 | await page.click('.fm-submit') 83 | await page.waitFor(6000) 84 | 85 | await asyncio.sleep(2) 86 | await page.goto(self.loginUrl) 87 | await page.waitFor(12000) 88 | await self.get_cookie(page) 89 | await page.waitFor(12000) 90 | await self.page_close(self.browser) 91 | return {'code': 200, 'msg': '登陆成功'} 92 | except: 93 | return {'code': -1, 'msg': '出错'} 94 | finally: 95 | await page.waitFor(3000) 96 | await self.page_close(self.browser) 97 | 98 | async def get_cookie(self,page): 99 | cookies_list = await page.cookies() 100 | cookies = '' 101 | for cookie in cookies_list: 102 | str_cookie = '{0}={1}; ' 103 | str_cookie = str_cookie.format(cookie.get('name'), cookie.get('value')) 104 | cookies += str_cookie 105 | print(cookies) 106 | return cookies 107 | 108 | async def page_evaluate(self, page): 109 | await page.evaluate('''() =>{ Object.defineProperties(navigator,{ webdriver:{ get: () => undefined } }) }''') 110 | await page.evaluate('''() =>{ window.navigator.chrome = { runtime: {}, }; }''') 111 | await page.evaluate( 112 | '''() =>{ Object.defineProperty(navigator, 'languages', { get: () => ['en-US', 'en'] }); }''') 113 | await page.evaluate( 114 | '''() =>{ Object.defineProperty(navigator, 'plugins', { get: () => [1, 2, 3, 4, 5,6], }); }''') 115 | await page.waitFor(3000) 116 | 117 | async def page_close(self, browser): 118 | for _page in await browser.pages(): 119 | await _page.close() 120 | await browser.close() 121 | 122 | def input_time_random(self): 123 | return random.randint(100, 151) 124 | 125 | def run(self): 126 | loop = asyncio.get_event_loop() 127 | i_future = asyncio.ensure_future(self.login()) 128 | loop.run_until_complete(i_future) 129 | return i_future.result() 130 | 131 | 132 | if __name__ == '__main__': 133 | Smt = Login(shopId=None) 134 | Smt.run() 135 | 136 | ``` 137 | 138 | 139 | 上面就是利用 `Pyppeteer` 进行自动登陆的脚本了 140 | 141 | 不管是想通过登陆获取 `cookie` 还是采用 `Pyppeteer` 做完一些列的操作,都需要先完成这一步 142 | 143 | -------------------------------------------------------------------------------- /docs/浏览器自动化/readme.md: -------------------------------------------------------------------------------- 1 | 在自动化办公中,浏览器页面自动化可谓是非常重要的一环,我们需要让浏览器重复性、机械化的执行编写好的指令。 2 | 3 | 而最主要的目的就是通过自动化操作浏览器来获取页面上的数据,比如抓取某类商品信息时需要不断的进行翻页操作等等。 4 | 5 | 其他的诸如定时登陆,自动填写表单信息,自动页面签到等都是一些比较常用的场景。 6 | 7 | > 最终的目的,就是为了解放双手,让机器代替人工去执行这些反复的机械化动作 8 | 9 | 10 | 当下 `Python` 主流的 `web` 浏览器驱动工具有 `Selenium`、`Pyppeteer` 等等。 11 | 12 | 我们就以这两款工具为例,谈一谈 `Python` 是如何 13 | 进行 `web` 自动化操作的。 14 | 15 | -------------------------------------------------------------------------------- /docs/浏览器自动化/selenium/images/Chromium.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PY-GZKY/PythonAutomatedOfficeGuide/014509edfeff065588a1391c5a3e116976a40531/docs/浏览器自动化/selenium/images/Chromium.png -------------------------------------------------------------------------------- /docs/浏览器自动化/selenium/images/浏览器版本.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PY-GZKY/PythonAutomatedOfficeGuide/014509edfeff065588a1391c5a3e116976a40531/docs/浏览器自动化/selenium/images/浏览器版本.png -------------------------------------------------------------------------------- /docs/浏览器自动化/selenium/images/浏览器驱动版本.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PY-GZKY/PythonAutomatedOfficeGuide/014509edfeff065588a1391c5a3e116976a40531/docs/浏览器自动化/selenium/images/浏览器驱动版本.png -------------------------------------------------------------------------------- /docs/浏览器自动化/selenium/selenium常用功能.md: -------------------------------------------------------------------------------- 1 | > 蓄力中 .... -------------------------------------------------------------------------------- /docs/浏览器自动化/selenium/一些案例.md: -------------------------------------------------------------------------------- 1 | ## 抓取淘宝商品信息 2 | 3 | ## 自动签到 4 | 5 | ## 点触验证码 -------------------------------------------------------------------------------- /docs/浏览器自动化/selenium/初始化selenium.md: -------------------------------------------------------------------------------- 1 | `Selenium` 是一个用于 `Web` 应用程序测试的工具。是的,本质上它是一款 `自动化测试` 工具。 2 | 3 | 官方给出的宣传语是 4 | ```shell 5 | "Selenium 测试直接运行在浏览器中,就像真正的用户在操作一样" 6 | ``` 7 | 8 | ### 跨平台 9 | `windows`、`linux`、`MAC` 10 | 11 | ### 支持多语言 12 | `C`、 `java`、`ruby`、`python`、`C#` ,你都可以通过 `selenium` 完成自动化测试任务 13 | 14 | ### 多浏览器 15 | `ie`、`firefox`、`safari`、`opera`、`chrome` 16 | 17 | ### 分布式 18 | 可以把测试用例分布到不同的测试机器的执行,相当于分发机的功能 19 | 20 | > 使用 `selenium` 可以相当轻松的进行 `web` 自动化操作 21 | 22 | ## 安装 `Selenium` 23 | 对于 `Python` 来说我们可以一如既往的使用 `pip` 进行安装 24 | ```shell 25 | pip install selenium 26 | ``` 27 | 28 | ## `浏览器` 和 `chromedriver` 驱动 29 | 30 | 31 | `selenium` 支持多种浏览器版本,这里作者推荐使用 `chrome` 或 `火狐` 浏览器。 32 | 33 | 由于众所周知的原因我们选择去淘宝镜像站 下载 [`chromedriver`](http://npm.taobao.org/mirrors/chromedriver/) 驱动 34 | 35 | 你需要打开 chrome 浏览器查看你的浏览器版本下载对应版本的 `chromedriver` 驱动 36 | 37 | 查看 `chrome` 浏览器版本 38 | 39 | ![](./images/浏览器版本.png ':size=70%') 40 | 41 | 42 | 根据浏览器版本下载对应的版本号 43 | 44 | ![](./images/浏览器驱动版本.png ':size=70%') 45 | 46 | > `火狐`浏览器驱动这里不作详述 47 | 48 | ## 测试启动 Selenium 49 | 50 | 假设您已经安装成功,并且安装目录位于 `D:/chrome/chromedriver/chromedriver.exe` 51 | 52 | ```python 53 | 54 | ``` 55 | 56 | 57 | -------------------------------------------------------------------------------- /docs/浏览器自动化/selenium/编写自动下载表格程序.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PY-GZKY/PythonAutomatedOfficeGuide/014509edfeff065588a1391c5a3e116976a40531/docs/浏览器自动化/selenium/编写自动下载表格程序.md -------------------------------------------------------------------------------- /docs/浏览器自动化/常用工具/readme.md: -------------------------------------------------------------------------------- 1 | > 整理一些平时开发用到的浏览器工具和文档,这些能让你快速定位或提升查找效率 2 | 3 | ## `浏览器在线工具` 4 | 5 | 6 | 7 |
8 | Docker 9 |
 10 | 
 11 | - [https://www.yuque.com/dongchongxiacao/ixm3g9/wgu0lv](https://www.yuque.com/dongchongxiacao/ixm3g9/wgu0lv)
 12 | 
 13 | 
14 |
15 | 16 | 17 |
18 | 编程参考和管理 19 |
 20 | 
 21 | - [东哥的技术专栏](https://www.weishidong.com/)
 22 | - [Python 编程参考](https://git-scm.com/book/zh/v2)
 23 | - [工程师绘图技](https://www.weishidong.com/docs/uml/)
 24 | 
25 |
26 | 27 | 28 |
29 | 在线教程和文档 30 |
 31 | 
 32 | - [Git中文教程](https://git-scm.com/book/zh/v2)
 33 | - [SVN中文手册](http://svnbook.red-bean.com/nightly/zh/index.html)
 34 | - [Python中文文档](https://docs.python.org/3/)
 35 | - [playwright文档](https://playwright.dev/python/docs/intro)
 36 | - [jQuery API中文文档](https://jquery.cuishifeng.cn/)
 37 | - [Nginx中文文档](https://www.nginx.cn/doc/index.html)
 38 | - [Kafka中文文档](https://kafka.apachecn.org/)
 39 | - [Mybatis中文文档](https://mybatis.org/mybatis-3/zh/index.html)
 40 | - [微信小程序官方文档](https://developers.weixin.qq.com/miniprogram/dev/framework/)
 41 | - [Nodejs中文教程文档](http://nodejs.cn/learn/)
 42 | - [Apache Web Server文档](http://httpd.apache.org/docs/)
 43 | - [Spring文档中文版](https://www.springcloud.cc/spring-reference.html)
 44 | - [Golang标准库文档中文版](https://studygolang.com/pkgdoc)
 45 | - [Java 8官方文档](https://docs.oracle.com/javase/8/docs/api/index.html)
 46 | - [Maven官方文档](http://maven.apache.org/guides/)
 47 | - [Tomcat 8官方文档](http://tomcat.apache.org/tomcat-8.0-doc/index.html)
 48 | - [Spring Boot官方文档](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/)
 49 | - [RabbitMQ官方文档](https://www.rabbitmq.com/documentation.html)
 50 | - [RocketMQ官方文档](http://rocketmq.apache.org/docs/quick-start/)
 51 | - [Dubbo中文文档](https://dubbo.apache.org/zh/docs/)
 52 | - [Netty官方文档](https://netty.io/wiki/index.html)
 53 | - [Elasticsearch官方文档](https://www.elastic.co/guide/en/elasticsearch/reference/current/index.html)
 54 | - [Spring Cloud官方文档](https://spring.io/projects/spring-cloud)
 55 | - [Docker官方文档](https://docs.docker.com/get-started/)
 56 | - [kubernetes中文文档](https://kubernetes.io/zh/docs/home/)
 57 | - [thymeleaf官方文档](https://www.thymeleaf.org/documentation.html)
 58 | - [Vue.js中文文档](https://cn.vuejs.org/v2/guide/)
 59 | - [React.js官方文档](https://reactjs.org/docs/getting-started.html)
 60 | - [Jenkins中文文档](https://www.jenkins.io/zh/doc/)
 61 | 
62 |
63 | 64 |
65 | 画图工具 66 |
 67 | 
 68 | - [在线画图工具ProcessOn](https://www.processon.com/)
 69 | - [在线画图工具draw.io](https://app.diagrams.net/)
 70 | - [在线思维导图工具](http://www.mindline.cn/webapp)
 71 | - [PlantUML在线编辑器](http://haha98k.com/)
 72 | 
73 |
74 | 75 |
76 | 在线编解码工具 77 |
 78 | 
 79 | - [BASE64加密解密](https://base64.supfree.net/)
 80 | - [MD5编码工具](https://www.zxgj.cn/g/md5)
 81 | - [AES/DES加解密](http://www.fly63.com/tool/cipher/)
 82 | - [JWT解码工具](http://jwt.calebb.net/)
 83 | - [ASCII编解码工具](https://www.matools.com/code-convert-ascii)
 84 | - [Unicode编解码工具](https://www.zxgj.cn/g/unicode)
 85 | - [UTF-8编解码工具](https://www.zxgj.cn/g/utf8)
 86 | - [字符串编解码工具](https://www.zxgj.cn/g/enstring)
 87 | - [URL编解码工具](http://tool.chinaz.com/tools/urlencode.aspx?jdfwkey=lbixz1)
 88 | 
89 |
90 | 91 |
92 | 在线转换工具 93 |
 94 | 
 95 | - [在线ASCII码对照表](http://www.fly63.com/tool/ascii/)
 96 | - [通用进制转换工具](https://www.zxgj.cn/g/jinzhi)
 97 | - [在线浮点数十进制转换](http://www.binaryconvert.com/)
 98 | - [RGB颜色转换](https://www.zxgj.cn/g/yansezhi)
 99 | - [时间戳转换工具](https://www.zxgj.cn/g/unix)
100 | - [计量单位换算工具](http://www.fly63.com/tool/unitable/)
101 | - [在线JSON解析](http://www.json.cn/)
102 | - [在线JS代码格式化工具](https://prettier.io/playground/)
103 | - [SQL压缩/格式化工具](https://www.zxgj.cn/g/sqlformat)
104 | - [JSON和XML在线转换](https://www.zxgj.cn/g/jsonxml)
105 | - [JSON/YAML在线转换](http://www.fly63.com/tool/jsonyaml/)
106 | - [人民币大小写转换工具](http://www.fly63.com/tool/renmingbi/)
107 | 
108 |
109 | 110 | 111 |
112 | 正则表达式工具 113 |
114 | 
115 | - [正则表达式可视化工具](https://jex.im/regulex/#!flags=&re=%5E(a%7Cb)*%3F%24)
116 | - [正则表达式调试工具](https://regexr.com/)
117 | 
118 |
119 | 120 |
121 | 网络工具 122 |
123 | 
124 | - [IP地址归属地查询](https://www.ip138.com/)
125 | - [IP地址查询](https://www.ipip.net/ip.html)
126 | - [HTTP在线接口测试工具](http://www.fly63.com/php/http/)
127 | 
128 |
129 | 130 |
131 | 在线编译运行工具 132 |
133 | 
134 | - [在线编译工具套装](https://c.runoob.com/)
135 | - [C#在线编译运行](https://rextester.com/)
136 | - [C/C++在线编译调试](https://www.onlinegdb.com/)
137 | 
138 |
139 | 140 | 141 |
142 | 可视化/格式化工具 143 |
144 | 
145 | - [在线前端编辑器](https://codepen.io/)
146 | - [在线数据可视化](https://flourish.studio/)
147 | - [在线CSS代码可视化工具](https://enjoycss.com/)
148 | - [XML格式化工具](https://www.zxgj.cn/g/xmlformat)
149 | 
150 |
151 | 152 |
153 | 在线生成器 154 |
155 | 
156 | - [UUID在线生成器](https://www.zxgj.cn/g/uuid)
157 | - [随机数生成器](https://www.zxgj.cn/g/suijishu)
158 | 
159 |
160 | 161 |
162 | 其他常用工具 163 |
164 | 
165 | - [在线Nginx配置工具](https://www.digitalocean.com/community/tools/nginx#?)
166 | - [在线对比工具](http://www.fly63.com/tool/textdiff/)
167 | - [在线Chrome浏览器插件](https://www.crx4chrome.com/)
168 | - [在线接口文档管理工具](http://www.docway.net/)
169 | 
170 |
171 | 172 | 173 |
174 | 在线素材库 175 |
176 | 
177 | - [免费透明背景图片素材](http://pngimg.com/)
178 | - [Emoji表情搜索](https://emoji.svend.cc/)
179 | - [Emoji表情包下载](https://emojiisland.com/)
180 | - [ICON图标在线下载](https://www.iconfinder.com/)
181 | - [open source icons](https://feathericons.com/)
182 | - [阿里巴巴矢量图标库](https://www.iconfont.cn/)
183 | - [表情包在线网站](https://fabiaoqing.com/)
184 | - [极简壁纸](https://bz.zzzmh.cn/)
185 | - [Wallpaper Abyss壁纸](https://wall.alphacoders.com/)
186 | - [免费PNG图片库](https://pluspng.com/)
187 | - [Pixabay图片素材库](https://pixabay.com/zh/)
188 | - [Unsplash图片素材库](https://unsplash.com/)
189 | - [Pexels图片素材库](http://www.pexels.com/)
190 | - [NASA图片视频素材库](https://images.nasa.gov/)
191 | 
192 |
193 | 194 | 195 |
196 | 设计制作类工具 197 |
198 | 
199 | - [在线PS](https://www.uupoop.com/#/old)
200 | - [在线音频剪辑](https://www.weixinsyt.com/)
201 | - [在线视频剪辑](https://www.kapwing.com/)
202 | - [免费logo在线制作](http://www.uugai.com/)
203 | - [艺术字体在线生成](https://www.qt86.com/)
204 | - [在线表格转换工具](https://tableconvert.com/)
205 | - [在线海报设计工具](https://www.designcap.com/)
206 | - [图片智能放大工具](https://bigjpg.com/)
207 | - [二维码美化器](https://mh.cli.im/)
208 | - [在线代码截图工具](https://carbon.now.sh/)
209 | - [在线抠图工具](https://www.remove.bg/zh)
210 | - [ICO图标在线生成](http://www.fly63.com/php/ico/)
211 | - [SVG转PNG工具](http://www.fly63.com/tool/svg2img/)
212 | - [视频转GIF工具](http://www.fly63.com/tool/giftxt/)
213 | - [二维码在线生成器](http://www.fly63.com/tool/ewm/)
214 | - [二维码在线解码](http://www.fly63.com/php/decoder/)
215 | 
216 |
217 | 218 |
219 | 写作辅助工具 220 |
221 | 
222 | - [在线字数统计](https://www.eteste.com/)
223 | - [mdnice markdown排版工具](https://mdnice.com/)
224 | - [md2all markdown排版工具](http://md.aclickall.com/)
225 | - [微信 markdown 编辑器](https://doocs.gitee.io/md/)
226 | - [图片上传 | PicX 图床神器](https://picx.xpoet.cn/)
227 | - [在线免费图床](https://sm.ms/)
228 | - [图壳图床](https://imgkr.com/)
229 | - [在线短链接工具](https://urlify.cn/)
230 | - [在线文本替换](http://www.fly63.com/tool/textreplace/)
231 | 
232 |
233 | 234 |
235 | 在线办公工具 236 |
237 | 
238 | - [pdf在线处理套装1](https://tools.pdf24.org/zh/)
239 | - [pdf在线处理套装2](https://smallpdf.com/cn/pdf-tools)
240 | - [在线多媒体转换器合集](https://cn.office-converter.com/)
241 | - [在线文字识别工具](https://ocr.wdku.net/)
242 | - [在线文件压缩工具](https://docsmall.com/)
243 | 
244 |
245 | 246 |
247 | 文档笔记工具 248 |
249 | 
250 | - [印象笔记](https://www.yinxiang.com/)
251 | - [有道笔记](https://note.youdao.com/)
252 | - [OneNote](https://www.onenote.com/)
253 | - [幕布](https://mubu.com/)
254 | - [为知笔记](https://www.wiz.cn/)
255 | - [石墨文档](https://shimo.im/)
256 | - [Simplenote](https://simplenote.com/)
257 | - [语雀](https://www.yuque.com/)
258 | 
259 |
260 | 261 |
262 | 在线编程学习 263 |
264 | 
265 | - [哔哩哔哩](https://www.bilibili.com/)
266 | - [C语言网](https://www.dotcpp.com/)
267 | - [cppreference](https://zh.cppreference.com/w/%E9%A6%96%E9%A1%B5)
268 | - [中国大学MOOC](https://www.icourse163.org/)
269 | - [网易公开课](https://open.163.com/)
270 | - [牛客网](https://www.nowcoder.com/)
271 | - [CodeGym](https://codegym.cc/)
272 | - [BeginnersBook](https://beginnersbook.com/)
273 | - [JavaSED](http://www.javased.com/)
274 | - [codecademy](https://www.codecademy.com/)
275 | - [Coursera](https://www.coursera.org/)
276 | - [StackOverFlow](https://stackoverflow.com/)
277 | - [LeetCode](https://leetcode-cn.com/)
278 | - [LintCode](https://www.lintcode.com/)
279 | 
280 |
281 | 282 | 283 | ## `文章收藏` 284 | [filename](./My.html ':include width=100% height=900px') 285 | 286 | -------------------------------------------------------------------------------- /docs/浏览器自动化/油猴插件/readme.md: -------------------------------------------------------------------------------- 1 | 油猴插件是一款各大主流浏览器常用的脚本插件,其作用可大可小,这里列举油猴插件在自动化方面的应用,与web自动化接轨。 2 | 3 | -------------------------------------------------------------------------------- /docs/爬虫/demo(抓取携程热门景点评论).md: -------------------------------------------------------------------------------- 1 | ## 分析需求 2 | 3 | ## 分析网页、制定方案 4 | 5 | ## 编写逻辑代码 6 | 7 | 首先、我们知道携程景点的 url 有很多个,鉴于这个情况我们使用 redis 数据库作为中间队列缓存这些 url。 8 | 当然如果只有十几个、几百个 url 并且看起来程序不会被意外退出的话也可以使用 Python 自带的 List、set(或者第三方库实现的queue) 等数据结构作为 队列 9 | 10 | redis 数据结构的话我们选择比较常用的 集合 就行。 11 | 12 | > 假定你已经在本地或者远程主机上安装了redis 数据库,请参考前面的 redis 数据库章节 13 | 14 | 接下来可以创建一个 redis 的操作实例。 15 | 16 | ```python 17 | # coding=utf-8 18 | import redis 19 | 20 | class RedisQueue(object): 21 | 22 | def __init__(self, **redis_kwargs): 23 | self.__db = redis.Redis(host='127.0.0.1', port=6379, db=15, password="admin") 24 | 25 | def sadd(self, name, v_): 26 | self.__db.sadd(name, v_) 27 | 28 | def spop(self, name): 29 | return self.__db.spop(name) 30 | 31 | def get_set_count(self, name): 32 | return self.__db.scard(name) 33 | 34 | def set_empty(self, name): 35 | return self.get_set_count(name) == 0 36 | 37 | # if __name__ == '__main__': 38 | # R = RedisQueue() 39 | # R.sadd() 40 | ``` 41 | 42 | 我们声明了 一个 sadd 和 一个 spop 方法分别用于添加队列元素和取出队列元素, 43 | 最后还实现了 set_empty 方法用于判断队列长度是否为 0 44 | 45 | 这样一来 我们提前将所有需要抓取的 url 链接放入到 redis 数据库中(这里我使用的是编号为 15 的数据库、你可以自行指定) 46 | 47 | 查看redis 数据库中的数据 ..... 48 | 49 | ## 数据查看和利用 50 | 51 | 52 | 53 | > 文档中代码仅供学习,请勿商用,作者保留著作权。 -------------------------------------------------------------------------------- /docs/爬虫/m3u8音视频拼接.md: -------------------------------------------------------------------------------- 1 | > https://play.fenyucn.com/m/t/S3NstazCETxIoEu8l-KSp22b1LdPkjr99oLGTZlGReo6g2Y-OfC7fL61CMCBtVPGCErpCGvSs3ipdA--jCJgLXoXGbz_3GdJu4PAIfGzIjWYgLVetPLZAfeP9OL5QUAF.m3u8 2 | 3 | `m3u8` 是一种又 `.ts` 音视频`片段`拼凑而成的文件,当浏览器加载 `.m3u8` 文件时,我们可以看到后台 `ajax` 是分一段一段加载的, 4 | 也就是看到哪加载到哪。 5 | 6 | `m3u8` 内容大致如下 7 | ```text 8 | #EXTM3U 9 | #EXT-X-VERSION:3 10 | #EXT-X-TARGETDURATION:5 11 | #EXT-X-MEDIA-SEQUENCE:0 12 | #EXTINF:5.000000, 13 | https://t.fenyucn.com//s18/M00/A1/87/CoUBYGB7E7iESQbUAAAAADjXebE233_transcode_133947_0.ts 14 | #EXTINF:5.000000, 15 | https://t.fenyucn.com//s18/M00/A1/87/CoUBYGB7E7iESQbUAAAAADjXebE233_transcode_133947_1.ts 16 | #EXTINF:5.000000, 17 | https://t.fenyucn.com//s18/M00/A1/87/CoUBYGB7E7iESQbUAAAAADjXebE233_transcode_133947_2.ts 18 | #EXTINF:5.000000, 19 | https://t.fenyucn.com//s18/M00/A1/87/CoUBYGB7E7iESQbUAAAAADjXebE233_transcode_133947_3.ts 20 | #EXTINF:5.000000, 21 | https://t.fenyucn.com//s18/M00/A1/87/CoUBYGB7E7iESQbUAAAAADjXebE233_transcode_133947_4.ts 22 | #EXTINF:5.000000, 23 | https://t.fenyucn.com//s18/M00/A1/87/CoUBYGB7E7iESQbUAAAAADjXebE233_transcode_133947_5.ts 24 | #EXTINF:5.000000, 25 | https://t.fenyucn.com//s18/M00/A1/87/CoUBYGB7E7iESQbUAAAAADjXebE233_transcode_133947_6.ts 26 | #EXTINF:5.000000, 27 | https://t.fenyucn.com//s18/M00/A1/87/CoUBYGB7E7iESQbUAAAAADjXebE233_transcode_133947_7.ts 28 | #EXTINF:5.000000, 29 | https://t.fenyucn.com//s18/M00/A1/87/CoUBYGB7E7iESQbUAAAAADjXebE233_transcode_133947_8.ts 30 | #EXTINF:2.466667, 31 | https://t.fenyucn.com//s18/M00/A1/87/CoUBYGB7E7iESQbUAAAAADjXebE233_transcode_133947_9.ts 32 | #EXT-X-ENDLIST 33 | ``` 34 | 35 | > 其中有用的就是像 `https://t.fenyucn.com//s18/M00/A1/87/CoUBYGB7E7iESQbUAAAAADjXebE233_transcode_133947_9.ts` 的 .ts 链接了。 36 | 37 | 我们需要将它下载下来,这里我提前存到本地。 38 | 39 | #### 提取 `.ts` 片段 40 | ```python 41 | # m3u8是本地的文件路径 42 | def get_ts_urls(m3u8_path): 43 | urls = [] 44 | with open(m3u8_path, "r") as file: 45 | lines = file.readlines() 46 | for line in lines: 47 | if line.endswith(".ts\n"): 48 | print(line) 49 | urls.append(line.strip("\n")) 50 | return urls 51 | ``` 52 | 53 | #### 下载 `.ts` 片段到指定文件夹 54 | ```python 55 | import datetime 56 | import time 57 | import os 58 | import requests 59 | 60 | def download(ts_urls, download_path): 61 | for i in range(len(ts_urls)): 62 | ts_url = ts_urls[i] 63 | file_name = ts_url.split("/")[-1] 64 | print("开始下载 %s" % file_name) 65 | try: 66 | response = requests.get(ts_url, stream=True, verify=False) 67 | except Exception as e: 68 | print("异常请求:%s" % e.args) 69 | return 70 | ts_path = download_path + "/{0}.ts".format(i) 71 | with open(ts_path, "wb+") as file: 72 | for chunk in response.iter_content(chunk_size=1024): 73 | if chunk: 74 | file.write(chunk) 75 | 76 | time.sleep(0.78) 77 | ``` 78 | 79 | #### 文件夹取出 `.ts` 文件并 `排序` 80 | ```python 81 | import os 82 | def file_walker(path): 83 | file_list = os.listdir(path) 84 | # file_list.sort() 85 | file_list.sort(key=lambda x: int(x[:-3])) 86 | file_list_ = [] 87 | for fn in file_list: 88 | p = str("tsfiles" + '/' + fn) 89 | file_list_.append(p) 90 | 91 | return file_list_ 92 | ``` 93 | 94 | #### 将 `.ts` 重新拼接到大文件中(`ts`/`mp4`) 95 | 96 | ```python 97 | def combine(ts_path, file_name): 98 | file_list = file_walker(ts_path) 99 | file_path = file_name + '.MP4' 100 | with open(file_path, 'wb+') as fw: 101 | for i in range(len(file_list)): 102 | fw.write(open(file_list[i], 'rb').read()) 103 | 104 | if __name__ == '__main__': 105 | urls = get_ts_urls("hhh.m3u8") 106 | download(urls, "tsfiles") 107 | combine("tsfiles", "大理") 108 | ``` 109 | 110 | ```python 111 | import datetime 112 | import time 113 | import os 114 | 115 | import requests 116 | 117 | # m3u8是本地的文件路径 118 | def get_ts_urls(m3u8_path): 119 | urls = [] 120 | with open(m3u8_path, "r") as file: 121 | lines = file.readlines() 122 | for line in lines: 123 | if line.endswith(".ts\n"): 124 | print(line) 125 | urls.append(line.strip("\n")) 126 | return urls 127 | 128 | 129 | def download(ts_urls, download_path): 130 | for i in range(len(ts_urls)): 131 | ts_url = ts_urls[i] 132 | file_name = ts_url.split("/")[-1] 133 | print("开始下载 %s" % file_name) 134 | try: 135 | response = requests.get(ts_url, stream=True, verify=False) 136 | except Exception as e: 137 | print("异常请求:%s" % e.args) 138 | return 139 | 140 | ts_path = download_path + "/{0}.ts".format(i) 141 | with open(ts_path, "wb+") as file: 142 | for chunk in response.iter_content(chunk_size=1024): 143 | if chunk: 144 | file.write(chunk) 145 | time.sleep(.56) 146 | 147 | 148 | def file_walker(path): 149 | file_list = os.listdir(path) 150 | # file_list.sort() 151 | file_list.sort(key=lambda x: int(x[:-3])) 152 | file_list_ = [] 153 | for fn in file_list: 154 | # print(fn) 155 | p = str("tsfiles" + '/' + fn) 156 | file_list_.append(p) 157 | 158 | print(file_list_) 159 | return file_list_ 160 | 161 | 162 | def combine(ts_path, file_name): 163 | file_list = file_walker(ts_path) 164 | file_path = file_name + '.MP4' 165 | with open(file_path, 'wb+') as fw: 166 | for i in range(len(file_list)): 167 | fw.write(open(file_list[i], 'rb').read()) 168 | 169 | 170 | if __name__ == '__main__': 171 | urls = get_ts_urls("hhh.m3u8") 172 | download(urls, "tsfiles") 173 | combine("tsfiles", "大理") 174 | ``` 175 | 176 | 177 | > 其中容易有变数的可能是 `.m3u8` 文件中的 `.ts` 链接有时候是不带`后缀`的,需要重新判断。 -------------------------------------------------------------------------------- /docs/爬虫/readme.md: -------------------------------------------------------------------------------- 1 | 2 | `爬虫`,也称`数据采集`。 3 | 4 | 作为`自动化`的延申,我觉得很有必要着重的讲一下`爬虫`与`自动化`的联系。 5 | 6 | 数据`爬虫`本质上也是`自动化`的一种,只不过数据`爬虫`追求一种更加高效,更加快速的抓取手段以达到目的。 7 | 8 | 这也是爬虫能从自动化操作中被人们拎出来单独作为一个领域存在的主要原因。 9 | 10 | 通过`Python`爬虫脚本,我们可以很快速的从目标站点获取到一些我们觉得有意义,有价值的数据。 11 | 12 | 当然这些站点或者平台上获取数据的难易程度不一,我们称之为反爬,有价值的网站都会有反爬机制 !! 13 | 14 | 有反爬虫,就会有反反爬虫,这是一个领域的对立面,是一场无休无止的斗争,而且日益强盛,每天都有新玩法。 15 | 16 | 这种对抗所带来的结果自然就是,反爬虫机制越来越严格,数据越来越受保护,而爬虫技术也是日新月异,层出不穷。 17 | 18 | 以至于近几年备受关注的`爬虫逆向工程师`的兴起,这几乎成为了另一个领域,一个爬虫界顶尖的领域。 19 | 20 | 顶尖的人才总是少数的,如果常规手段的爬虫不能让工程师们达到满意的效果, 他们开始尝试结合自动化工具进行数据提取。 21 | 22 | 诸如 `Selenium`、 `Pyppeteer`、 `Appium` 等等都将成为爬虫工程师的选择。 23 | 24 | 而对于本文档来说,我希望将数据爬虫作为 `Python` 自动化的延申,希望在数据抓取方面能对你产生帮助。 25 | 26 | 当然这里作者不会采用过于高深难懂的技术,读者大可不必担心门槛过高等问题。 27 | 28 | -------------------------------------------------------------------------------- /docs/爬虫/代理服务器.md: -------------------------------------------------------------------------------- 1 | #### 隧道IP: 动态切换目标`代理ip`,每次发出请求的`ip`都不相同 2 | #### 直连IP: 和`ip`供应商同属一条网线,对连模式(限制性较高) 3 | #### 独享IP: 供应商分配的独有的一小批优质`代理ip`,目前价格最高,也是最稳定的。 4 | 5 | #### 推荐代理供应商: `芝麻代理`、 `阿布云代理`、 快代理、 站大爷 .... 6 | 7 | > 随着大量代理服务器ip的供应商涌入,现在已经几乎没人去自行搭建代理ip池了。 8 | 9 | #### 使用芝麻代理(Python-requests) 10 | 11 | ```python 12 | import requests 13 | from requests.exceptions import ConnectionError 14 | import urllib3 15 | urllib3.disable_warnings() 16 | 17 | class C_IP(): 18 | def __init__(self): 19 | self.headers = { 20 | "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.107 Safari/537.36" 21 | } 22 | self.r = requests.session() 23 | 24 | def get_ip(self, url): 25 | params = None 26 | proxies = None 27 | try: 28 | req = self.r.get(url, headers=self.headers, params=params, proxies=proxies, verify=False, timeout=60) 29 | if req.status_code == 200: 30 | req.encoding = req.apparent_encoding 31 | return req.json() 32 | return None 33 | except ConnectionError: 34 | return None 35 | 36 | def get_html(self, url, ip_port="140.249.73.93:4821"): 37 | params = None 38 | proxies = { 39 | 'http': f'http://{ip_port}', 40 | 'https': f'https://{ip_port}' 41 | } 42 | try: 43 | req = self.r.get(url, headers=self.headers, params=params, proxies=proxies, verify=False, timeout=60) 44 | if req.status_code == 200: 45 | req.encoding = req.apparent_encoding 46 | return req.text 47 | return None 48 | except ConnectionError: 49 | return None 50 | 51 | def run(self): 52 | # get_ip_result = self.get_ip(url="http://zhima.api/") 53 | # print(get_ip_result) 54 | # ip = get_ip_result.get("data")[0].get("ip") 55 | # port = get_ip_result.get("data")[0].get("port") 56 | # ip_port = f"{ip}:{port}" 57 | # print(ip_port) 58 | 59 | # get_result = self.get_html(url="http://httpbin.org/get",ip_port=ip_port) 60 | # print(get_result) 61 | 62 | get_result = self.get_html(url="https://api.ipify.org/?format=json", ip_port="111.72.136.127:4230") 63 | print(get_result) 64 | 65 | 66 | if __name__ == '__main__': 67 | G = C_IP() 68 | G.run() 69 | ``` 70 | 71 | 72 | #### 而对于使用`代理ip` 的策略,主要还是看其使用的场景,对症下药。以求代理 `ip` 使用率最大化。 73 | 74 | - 检测`代理ip` 是否过期以及`重试`次数 75 | - 针对不同的`网站`制定不同的请求策略、`ip时长`的选取、`区域`的选取 76 | - 维护可用的`代理ip`列表 77 | - 不要妄想着使用`免费`代理 78 | -------------------------------------------------------------------------------- /docs/爬虫/工具简介.md: -------------------------------------------------------------------------------- 1 | > 在爬虫领域中,优秀的辅助工具往往能事半功倍。 2 | 3 | ## `抓包工具` 4 | ### `Chrome` 调试器 5 | 毋庸置疑的是 `chrome` 浏览器自带的控制台调试器 (`F12`) 非常强大,它为`web`开发者提供了很大的便利,同时也是`爬虫`开发中最常用的工具 (近水楼台先得月)。 6 | 7 | 这里没有谈及其他浏览器的调试器不是因为它们不够优秀,只是 `chrome` 更优秀。比如`Firefox` 浏览器自带的`F12`功能也很强大,这也是我次推的浏览器。对于浏览器内核我只认 `chrome` 和 `Firefox`。 8 | 9 | 10 | ### `Charles` 11 | 不管是`移动端`还是`PC端`,`Charles` 都是一大抓包利器,至少在`爬虫`领域,`Charles` 可谓有口皆碑,受众程度高。 12 | 13 | ### `Fiddler Everywhere` 14 | ### `Mitmproxy` 15 | 16 | ## `数据可视化工具` 17 | ### `Navicat` (`Mysql`、`MongoDB`) 18 | ### `DataGrip` (`Mysql`) 19 | ### (`MongoDB`) 20 | ### `RedisDesktopManager` (`Redis`) 21 | ### `JsonFormat` (`Json`) 22 | 23 | 24 | ## `API` 测试工具 25 | 26 | ### `Postman` 27 | 说到 `API` 测试,恐怕除了专业的测试员,大多数开发者都会选择 `Postman`, 28 | 但是我个人不喜欢 `Postman`,这里不想介绍。 29 | 30 | ### `Apifox` 31 | 32 | > `Apifox` 是接口管理、开发、测试全流程集成工具,定位 `Postman` + `Swagger` + `Mock` + `JMeter`。通过一套系统、一份数据,解决多个系统之间的数据同步问题。只要定义好接口文档,接口调试、数据 Mock、接口测试就可以直接使用,无需再次定义;接口文档和接口开发调试使用同一个工具,接口调试完成后即可保证和接口文档定义完全一致。高效、及时、准确! -- 引自 `Apifox` 官方文档 33 | 34 | > https://www.apifox.cn/help 35 | 36 | 37 | ## `SSH` 38 | ### `FinalShell` 39 | ### `Xshell` -------------------------------------------------------------------------------- /docs/爬虫/数据抓取入门.md: -------------------------------------------------------------------------------- 1 | 2 | > 在开始之前,我们需要安装一些必要的第三方扩展库 3 | 4 | ```shell 5 | pip install requests 6 | pip install lxml 7 | ``` 8 | `Requests` 是 `Python` 爬虫中的一把利器,如果你不是选择诸如 `Scrapy` 这样的爬虫框架的话,那么 `Requests` 可能是你最好的选择。 9 | 当然还有其他优秀的扩展库。 10 | 11 | 此文档重点不在 `Python`爬虫领域,而在于自动化相关,所以不会涉及类如 `Scrapy`、`PySpider`、`Celery` 这些框架的的讲解,不用担心 !! 12 | 13 | 我们通过一下的例子来初步了解 `Requests` 库的用法,爬虫爱好者们总是酷爱使用`豆瓣`网站来作为他们的第一个目标网站。 14 | 15 | 我们将通过 [豆瓣-正在热映](https://movie.douban.com/cinema/nowplaying/guangzhou/) 这个页面提取到正在上映电影 16 | 的名称,图片,评分,以及详情链接。 17 | 18 | 19 | ## 获取网页源码 20 | 21 | 22 | 代码如下: 23 | 24 | ```python 25 | 啪啪啪 这是代码 26 | 27 | ``` 28 | 29 | 得到了以下一些输出: 30 | ```text 31 | balaaaaaaa 32 | ``` 33 | 34 | 可以看到我们成功了打印出了这个页面的 `html` 源码,完成了第一步。 35 | 36 | ## 解析网页源码 37 | 38 | 现在我们对已经获取的 `html` 源码进行解析,这里以 `xpath` 为例, 常用的解析库还有`bs4`、`Pyquery`、`css选择器等等`, 39 | 有兴趣的读者可以自行搜索并进行比较,目前来说,`xpath`的解析速度是最快的。 40 | 41 | ```python 42 | 这里对豆瓣网页源码进行解析 43 | ``` 44 | 45 | 可以看到经过我们的解析之后,提取的数据变得清晰有结构,只提取保留对我们有用的信息。 46 | 47 | ## 获取 `Json` 结构化数据 48 | 49 | -------------------------------------------------------------------------------- /docs/调式和捕捉错误/dostest.md: -------------------------------------------------------------------------------- 1 | 作为一名程序员或者伪程序员,想要让自己写出来代码更加健壮,测试用例是必要的。 2 | > [https://zhuanlan.zhihu.com/p/196598169](https://zhuanlan.zhihu.com/p/196598169) 3 | 4 | ## 单元测试 5 | 6 | ## 文档测试 7 | 8 | 9 | -------------------------------------------------------------------------------- /docs/调式和捕捉错误/readme.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ## 常见异常 5 | 6 | - `AttributeError`: 调用不存在的方法引发的异常 7 | - `EOFError`: 遇到文件末尾引发的异常 8 | - `ImportError`: 导入模块出错引发的异常 9 | - `IndexError`: 列表越界引发的异常 10 | - `IOError`: `I/O` 操作引发的异常,如打开文件出错等 11 | - `KeyError`: 使用字典中不存在的关键字引发的异常 12 | - `NameError`: 使用不存在的变量名引发的异常 13 | - `TabError`: 语句块缩进不正确引发的异常 14 | - `ValueError`: 搜索列表中不存在的值引发的异常 15 | - `ZeroDivisionError`: 除数为零引发的异常 16 | 17 | 18 | ## traceback模块 19 | 20 | > `traceback`: 打印或读取堆栈的跟踪信息 21 | 22 | ```python 23 | try: 24 | 1/0 25 | except Exception as e: 26 | print(e) 27 | ``` 28 | 29 | 首先,上面的代码只能帮助我们得到 `division by zero` 错误,但是我们并不知道是在哪个文件哪个函数哪一行出的错 30 | 31 | ```python 32 | import traceback 33 | try: 34 | 1/0 35 | except Exception as e: 36 | traceback.print_exc() 37 | ``` 38 | 39 | 用 `traceback` 模块能自主的追溯到执行出错的准确地方 40 | 41 | ```text 42 | Traceback (most recent call last): 43 | File "_.py", line 4, in 44 | 1/0 45 | ZeroDivisionError: division by zero 46 | ``` 47 | 48 | 关于 的一些基本用法: 49 | 50 | ```python 51 | import sys, traceback 52 | 53 | def lumberjack(): 54 | return tuple()[0] 55 | 56 | try: 57 | lumberjack() 58 | except IndexError: 59 | exc_type, exc_value, exc_traceback = sys.exc_info() 60 | # print(exc_type, exc_value, exc_traceback) 61 | 62 | # print("*** print_tb:") 63 | # traceback.print_tb(exc_traceback, limit=4, file=sys.stdout) 64 | 65 | # print("*** print_exception:") 66 | # traceback.print_exception(exc_type, exc_value, exc_traceback,limit=2, file=sys.stdout) 67 | 68 | print("*** print_exc:") 69 | traceback.print_exc(limit=2, file=sys.stdout) 70 | 71 | print("*** format_exc:") 72 | format_exc = traceback.format_exc() 73 | print(format_exc) 74 | ``` 75 | 76 | `format_exc()` 返回字符串,`print_exc()`则直接给打印出来。即`traceback.print_exc()`与`print(traceback.format_exc())`效果是一样的。`print_exc()`还可以接受`file`参数直接写入到一个文件。比如可以像下面这样把相关信息写入到`tb.txt`文件去 77 | 78 | ```python 79 | traceback.print_exc(file=open('tb.txt','w+')) 80 | ``` 81 | 82 | 当然也可以直接打印和格式化堆栈 83 | 84 | ```python 85 | traceback.print_stack() 86 | print(repr(traceback.extract_stack())) 87 | print(repr(traceback.format_stack())) 88 | ``` 89 | 90 | `traceback` 对程序运行异常的捕获或读取堆栈的跟踪信息具有很大的意义 91 | 92 | ## 如何在 `Python` 中进行代码调试 93 | 94 | `debug`(程序调试) 是一个程序员的基本能力,同时也是一项重要的技能。 95 | `debug`能让你正确的知道 `Python` 代码在运行过程中到底发生了什么 96 | 97 | - `Bug` 臭虫,功能性、逻辑性错误 98 | - `Debug` 调试程序,找到程序`bug`的过程 99 | 100 | 101 | ## `断言`和`异常`抛出 102 | 103 | 我们来看一段简单的具有潜在 `bug` 的程序 104 | 105 | ```python 106 | def isBug(x, y): 107 | return x / y 108 | 109 | print(isBug(16, 4)) 110 | ``` 111 | 112 | 我们都知道在进行除法的时候分母不为 `0`,当分母为 `0` 时将会抛出`异常` 113 | 114 | ```shell 115 | In [12]: print(isBug(1, 0)) 116 | --------------------------------------------------------------------------- 117 | ZeroDivisionError Traceback (most recent call last) 118 | in 119 | ----> 1 print(isBug(1, 0)) 120 | 121 | in isBug(x, y) 122 | 1 def isBug(x, y): 123 | ----> 2 return x / y 124 | 3 125 | 126 | ZeroDivisionError: division by zero 127 | ``` 128 | 129 | ## 程序调试 130 | #### 最简单的 `Print` 131 | `print` 可能是你初识 `Python` 之后用的第一个函数,它能最直观的把日志信息输出到控制台。 132 | 133 | 如果我们需要更多格式和等级的日志输出,则会用到一些第三方的日志库,这里推荐 134 | ## Pycharm断点调试 -------------------------------------------------------------------------------- /docs/调式和捕捉错误/traceback.md: -------------------------------------------------------------------------------- 1 | ## 常见异常 2 | 3 | - `AttributeError`: 调用不存在的方法引发的异常 4 | - `EOFError`: 遇到文件末尾引发的异常 5 | - `ImportError`: 导入模块出错引发的异常 6 | - `IndexError`: 列表越界引发的异常 7 | - `IOError`: `I/O` 操作引发的异常,如打开文件出错等 8 | - `KeyError`: 使用字典中不存在的关键字引发的异常 9 | - `NameError`: 使用不存在的变量名引发的异常 10 | - `TabError`: 语句块缩进不正确引发的异常 11 | - `ValueError`: 搜索列表中不存在的值引发的异常 12 | - `ZeroDivisionError`: 除数为零引发的异常 13 | 14 | 15 | ## traceback模块 16 | 17 | > `traceback`: 打印或读取堆栈的跟踪信息 18 | 19 | ```python 20 | try: 21 | 1/0 22 | except Exception as e: 23 | print(e) 24 | ``` 25 | 26 | 首先,上面的代码只能帮助我们得到 `division by zero` 错误,但是我们并不知道是在哪个文件哪个函数哪一行出的错 27 | 28 | ```python 29 | import traceback 30 | try: 31 | 1/0 32 | except Exception as e: 33 | traceback.print_exc() 34 | ``` 35 | 36 | 用 `traceback` 模块能自主的追溯到执行出错的准确地方 37 | 38 | ```text 39 | Traceback (most recent call last): 40 | File "_.py", line 4, in 41 | 1/0 42 | ZeroDivisionError: division by zero 43 | ``` 44 | 45 | 关于 的一些基本用法: 46 | 47 | ```python 48 | import sys, traceback 49 | 50 | def lumberjack(): 51 | return tuple()[0] 52 | 53 | try: 54 | lumberjack() 55 | except IndexError: 56 | exc_type, exc_value, exc_traceback = sys.exc_info() 57 | # print(exc_type, exc_value, exc_traceback) 58 | 59 | # print("*** print_tb:") 60 | # traceback.print_tb(exc_traceback, limit=4, file=sys.stdout) 61 | 62 | # print("*** print_exception:") 63 | # traceback.print_exception(exc_type, exc_value, exc_traceback,limit=2, file=sys.stdout) 64 | 65 | print("*** print_exc:") 66 | traceback.print_exc(limit=2, file=sys.stdout) 67 | 68 | print("*** format_exc:") 69 | format_exc = traceback.format_exc() 70 | print(format_exc) 71 | ``` 72 | 73 | `format_exc()` 返回字符串,`print_exc()`则直接给打印出来。即`traceback.print_exc()`与`print(traceback.format_exc())`效果是一样的。`print_exc()`还可以接受`file`参数直接写入到一个文件。比如可以像下面这样把相关信息写入到`tb.txt`文件去 74 | 75 | ```python 76 | traceback.print_exc(file=open('tb.txt','w+')) 77 | ``` 78 | 79 | 当然也可以直接打印和格式化堆栈 80 | 81 | ```python 82 | traceback.print_stack() 83 | print(repr(traceback.extract_stack())) 84 | print(repr(traceback.format_stack())) 85 | ``` 86 | 87 | `traceback` 对程序运行异常的捕获或读取堆栈的跟踪信息具有很大的意义 88 | 89 | ## 如何在 `Python` 中进行代码调试 90 | 91 | `debug`(程序调试) 是一个程序员的基本能力,同时也是一项重要的技能。 92 | `debug`能让你正确的知道 `Python` 代码在运行过程中到底发生了什么 93 | 94 | - `Bug` 臭虫,功能性、逻辑性错误 95 | - `Debug` 调试程序,找到程序`bug`的过程 96 | 97 | 98 | ## `断言`和`异常`抛出 99 | 100 | 我们来看一段简单的具有潜在 `bug` 的程序 101 | 102 | ```python 103 | def isBug(x, y): 104 | return x / y 105 | 106 | print(isBug(16, 4)) 107 | ``` 108 | 109 | 我们都知道在进行除法的时候分母不为 `0`,当分母为 `0` 时将会抛出`异常` 110 | 111 | ```shell 112 | In [12]: print(isBug(1, 0)) 113 | --------------------------------------------------------------------------- 114 | ZeroDivisionError Traceback (most recent call last) 115 | in 116 | ----> 1 print(isBug(1, 0)) 117 | 118 | in isBug(x, y) 119 | 1 def isBug(x, y): 120 | ----> 2 return x / y 121 | 3 122 | 123 | ZeroDivisionError: division by zero 124 | ``` 125 | 126 | ## 程序调试 127 | #### 最简单的 `Print` 128 | `print` 可能是你初识 `Python` 之后用的第一个函数,它能最直观的把日志信息输出到控制台。 129 | 130 | 如果我们需要更多格式和等级的日志输出,则会用到一些第三方的日志库,这里推荐 131 | ## Pycharm断点调试 -------------------------------------------------------------------------------- /docs/软件/readme.md: -------------------------------------------------------------------------------- 1 | ## 主流的 rpa 软件或工具 2 | 3 | 国内外 `rpa` 软件比比皆是,国外的 `rpa` 代表有 `AA`,`UIPATH`, 国内有 `uibot`, 4 | `影刀rpa`,`阿里rpa`, `一赛奇`等等...... 5 | 6 | 其中`AA`和`UIPATH`是老国外相对较老的平台了,这里不做比较,国内软件从稳定性来看当属`影刀rpa`,`uibot`虽然功能齐全但欠缺稳定性,`阿里rpa` 虽然也支持了`Python扩展`但是产品的迭代出现了问题。 7 | 8 | 9 | 而影刀作为新生代的 `rpa` 解决了前面几个软件的弊端,不仅功能齐全,界面美观度高,还支持多机器人应用,`Python` 扩展以及第三方插件,灵活度较高。当然影刀集成的这一套 `rpa` 流程化应用和服务, 10 | 主要面向企业应用,体验版本是免费的,但是功能首先,个人版约 2500/年,功能齐全但是没有控制台统一调度和配置,无法享受专业化的售后定制服务,目前企业版没有明码标价,需要联系影刀官方洽谈。 11 | 12 | 选择`影刀rpa` 很大程度上是因为它采用了 `Python` 引擎作为扩展,而 `Python` 在自动化处理方面正发挥着强大的威力!!!! -------------------------------------------------------------------------------- /docs/软件/影刀RPA/readme.md: -------------------------------------------------------------------------------- 1 | 2 | ### 什么是影刀 3 | 一款作用于 `Windows` 系统上的 `rpa` 软件,目前暂不支持 Mac,自动执行各类 Windows、手机端上的任务,在系统之间进行数据传输、任何桌面软件或Web应用程序进行交互。 4 | 5 | 积木式的流程化搭建,采用控件拖拽的方式处理自动化流程,基于图像识别控件,总体上来说还是非常精准有效的。 6 | 7 | 支持定时任务,日志报告,邮件推送等等,企业版可使用控制台交互和调度机器人配置。 8 | 9 | 10 | ### 工作区 11 | 影刀带有强有力的 Python 作为扩展语言,这对 Python 开发者是一个福音,特别是想往或者正工作与 Python 自动化开发的小伙伴,简单易上手的影刀软件加上Python的扩展,基本上可以完成他们在 Windows 下的常规工作。 12 | 13 | ### 编码模式 14 | 影刀 rpa 可以在流程化中新建或者打开已有的Python模块进行代码的编写,支持在线安装第三方库,诸如 requests,pymysql之类的扩展库,它会独立集成一个 Python 环境,在影刀上做的所有关于 Python 的操作不会影响到你本地的 Python 环境。 15 | 当然影刀也自带了一些默认的 Python 包,比如 package、 xbot 等等 16 | 编码模式的工程目录结构一般是: 主流程和子流程图、引用、资源文件、Python模块文件,当然我们可以新建文件夹目录来编写其他模块共主模块调用,同时可以自定义指令,所谓的指令就相当于 Python 中的定义的一个方法,方法的复用就是指令的存在。 17 | 18 | ### 控件流程化 19 | 20 | 影刀提供了一系列的指令集合,采用拖拽的方式搭建流程化工程,指令集涵盖了网页自动化,桌面应用自动化,自动邮件推送,数据表格,系统调度,集成了条件判断,循环遍历的语法, 21 | 流程模块的后缀为 .frow,顾名思义就是流处理,流处理也分主流程和子流程,你可以理解为主流程就是主入口文件,而子流程则是具体的功能实现,主流程是一系列子流程的集合, 22 | 举个例子,我们编写了一个自动发送钉钉消息的流程应用,需要向钉钉指定的聊天人轰炸信息1000次,就可以将具体发消息的流程写在一个子流程里,而主流程用于编写 for 循环语句遍历 1000 次子流程, 23 | 24 | ### 混合开发 25 | 26 | 在流程化中可以调用 Python模块,在实际应用中可以交叉使用。 27 | 这是影刀rpa 和 其他rpa工具不同的地方,它支持编码的独立运行和混合开发,丰富了我们的应用场景。 28 | 比如我们可以定义一个采集网页数据的流处理,采集下来的数据需要进行初步清洗并存入远程数据库中,那我们就可以编写两个 Python 脚本,一个用于数据清洗,另一个将数据存入数据库,在流程中直接调用这两个模块并且传入采集的数据,做到了采集和入库的一步到位。 29 | 30 | 31 | ### 全局变量 32 | 33 | 全局变量用于各流程和模块之间的通信,比如上面的例子,采集的数据作为一个全局变量的形式存在,Python 模块获取到这个变量之后存入数据库。 34 | 35 | 全局变量支持多种数据类型,几乎涵盖了所有 windows 中可能出现的数据类型和文件类型 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /docs/软件/影刀RPA/天猫后台自动提报/images/Snipaste_2021-04-02_14-29-17.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PY-GZKY/PythonAutomatedOfficeGuide/014509edfeff065588a1391c5a3e116976a40531/docs/软件/影刀RPA/天猫后台自动提报/images/Snipaste_2021-04-02_14-29-17.png -------------------------------------------------------------------------------- /docs/软件/影刀RPA/天猫后台自动提报/images/Snipaste_2021-04-02_14-29-52.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PY-GZKY/PythonAutomatedOfficeGuide/014509edfeff065588a1391c5a3e116976a40531/docs/软件/影刀RPA/天猫后台自动提报/images/Snipaste_2021-04-02_14-29-52.png -------------------------------------------------------------------------------- /docs/软件/影刀RPA/天猫后台自动提报/天猫后台自动化提报操作文档.md: -------------------------------------------------------------------------------- 1 | # 天猫后台自动化提报操作文档 2 | 3 | > Created: 2021/04/02 4 | 5 | > Created By: tong wu 6 | 7 | > Last Edited Time: Apr 2, 2021 2:31 PM 8 | 9 | > Participants: tong wu 10 | 11 | > Type: 自动化 12 | 13 | 请安装影刀 RPA 并初步阅读影刀文档 !! 14 | 15 | [影刀RPA官网_每一个都能的用的RPA,新一代办公机器人流程自动化](https://www.winrobot360.com/) 16 | 17 | 影刀官网下载安装影刀RAP工具 18 | 19 | [影刀文档](https://www.winrobot360.com/doc/) 20 | 21 | 影刀帮助文档 22 | 23 | 获取天猫自动提报机器人应用或其他应用 24 | 25 | 1. 联系管理员开通影刀企业版子账号 26 | 2. 登陆控制台拉取自动提报机器人应用或其他应用到个人影刀账号 27 | 3. 根据此文档说明操作机器人 28 | 29 | ### 操作文档说明 30 | 31 | 1. 批量上传的 `excel` 文件统一固定在 `C:\Users\你的用户名\Documents\uploadExcel\`文件夹中, 请创建好文件夹 32 | 2. 在 `uploadExcel 文件夹中`新建文件分别为 `d58bec20950daca9f09cd882b93990c8.xlsx` 和 `套餐价格表-机器人提报.xlsx` 两个文件分别存放 `商品ID` 和 `SKUID` 信息 33 | 3. 通过提报场次 ID 确定提报场次 34 | 4. 如何获取 提报场次ID 35 | 5. 如果机器人设置为定时任务,需要提前打开影刀工具并确保本地电脑为活动状态(非关机和睡眠状态) 36 | 37 | ⚠️ 上传的路径和名称不可修改,机器人运行时表格文件保持非编辑状态 !!! 38 | 39 | ⚠️ 上传表格中 `商品ID` 和 `SKUID` 表格字段为 文本字段 而非数字 !!! 40 | 41 | ⚠️ 保证表格中的 `商品ID` 和 `SKUID` 和要在页面上输入框是对应的(实时更新sku表)!!! 42 | 43 | ⚠️ 机器人操作过程中尽量减少人工干预,减少不必要的异常中断 !!! 44 | 45 | ### 查看日志说明 46 | 47 | 🏃 运行时可查看下方日志输出,如有出错可及时查看 48 | 49 | 查看详细运行日志,方便排查,我将尽可能的将每一步的信息打印出来,方便查看 50 | 51 | ![](./images/Snipaste_2021-04-02_14-29-52.png) 52 | 53 | ### 🖕 请留意文档说明和机器人中的交互信息,操作将变得非常顺畅 -------------------------------------------------------------------------------- /docs/软件/阿里RPA/阿里RPA.md: -------------------------------------------------------------------------------- 1 | ## 阿里RPA 工具使用教程 2 | 3 | > `机器人流程自动化RPA` 是阿里云推出的一款自动化工具,底层使用.Net 和 C 开发,Python引擎运行 4 | 5 | > https://help.aliyun.com/document_detail/174946.html?spm=a2c4g.11186623.6.564.6ec71107AwdgA8 6 | 7 | ### 安装 RPA4.0 客户端 8 | 9 | 10 | ### 编辑器 11 | {docsify-updated} 12 | 13 | ### 机器人 14 | 15 | ### 控制台 16 | 17 | ### Python扩展 18 | 通过编辑器,我们知道阿里的自动化机器人是用 Python 作为脚本语言开发的,这对 Python 开发者来说是个福音。 19 | 20 | ### 如何执行 Python 扩展 21 | 除了阿里RPA 工具自带的Python 库,有时候(其实是绝大部分时候)我们需要使用更多的库和插件来支撑我们的业务需要。 22 | 23 | 比如 24 | - 获取到的数据需要持久化到数据库中,可能就需要用到 Pymongo、Pymysql、Redis 等库 25 | - 拿到表格数据之后需要处理,就需要用到 Pandas、 26 | - 碰到异常报错,可能用到 requests 库来给我们发送警告等等 27 | 28 | 阿里RPA 平台提供了扩展库的安装包 29 | 30 | 31 | ### 编写脚本 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /docs/错误页面/_404.md: -------------------------------------------------------------------------------- 1 | > 你来到了一个 `404` 页面 --------------------------------------------------------------------------------