0%

安装配置

Scrapy-Splash 是一个 Scrapy 中支持 JavaScript 渲染的工具,本节来介绍一下它的安装方式。

Scrapy-Splash 的安装分为两部分,一个是是 Splash 服务的安装,安装方式是通过 Docker,安装之后会启动一个 Splash 服务,我们可以通过它的接口来实现 JavaScript 页面的加载。另外一个是 ScrapySplash 的 Python 库的安装,安装之后即可在 Scrapy 中使用 Splash 服务。

相关链接

  • GitHub:https://github.com/scrapy-plugins/scrapy-splash
  • PyPi:https://pypi.python.org/pypi/scrapy-splash
  • 使用说明:https://github.com/scrapy-plugins/scrapy-splash#configuration
  • Splash 官方文档:http://splash.readthedocs.io

安装 Splash

Scrapy-Splash 会使用 Splash 的 HTTP API 进行页面渲染,所以我们需要安装 Splash 来提供渲染服务,安装教程参考:https://setup.scrape.center/splash

Scrapy-Splash 的安装

成功安装了 Splash 之后,我们接下来再来安装一下其 Python 库,安装命令如下:

1
pip3 install scrapy-splash

命令运行完毕后就会成功安装好此库。

完毕!

安装配置

成功安装好了 Selenium 库,但是它是一个自动化测试工具,需要浏览器来配合它使用,那么本节我们就介绍一下 Chrome 浏览器及 ChromeDriver 驱动的配置。

首先需要下载一个 Chrome 浏览器,方法多样,在此不再赘述。

随后我们需要安装一个 ChromeDriver 才能驱动 Chrome 浏览器完成相应的操作,下面我们来介绍下怎样安装 ChromeDriver。

相关链接

  • 官方网站:https://sites.google.com/a/chromium.org/chromedriver
  • 下载地址:https://chromedriver.storage.googleapis.com/index.html

准备工作

在这之前请确保已经正确安装好了 Chrome 浏览器并可以正常运行,安装过程不再赘述。

查看版本

点击 Chrome 的菜单,帮助->关于 Chrome,即可查看 Chrome 的版本号,在这里我的版本是 94.0.4606,如图所示:

请记住 Chrome 版本号,在后面选择 ChromeDriver 版本时需要用到。

下载 ChromeDriver

打开 ChromeDriver 的官方网站,链接为:https://sites.google.com/chromium.org/driver/downloads。可以看到到目前为止最新支持的 Chrome 浏览器版本为 95,最新版本以官网为准,如图所示:

每个版本都有相应的支持 Chrome 版本介绍,请找好自己的 Chrome 浏览器版本对应的 ChromeDriver 版本再下载,否则可能导致无法正常工作。

由于我这边的 ChromeDriver 版本是 94.0.4606,找到对应的下载列表,如图所示:

Windows 系统就下载 win32.zip,Mac 系统 Intel 芯片下载 mac64.zip,Mac 系统 M1 芯片下载 mac64_m1.zip,Linux 系统下载 linux64.zip,下载解压之后会得到一个 ChromeDriver 的可执行文件。

另外如果上面的链接打不开的话,也可以从对应的 ChromeDriver 镜像网站下载:https://chromedriver.storage.googleapis.com/index.html,同样地,版本对应好即可。

环境变量配置

下载完成后将 ChromeDriver 的可执行文件配置到环境变量下。

在 Windows 下,建议直接将 chromedriver.exe 文件拖到 Python 的 Scripts 目录下,如图所示:

也可以单独将其所在路径配置到环境变量,环境变量的配置方法请参见 Python3 的安装一节。

在 Linux、Mac 下,需要将可执行文件配置到环境变量或将文件移动到属于环境变量的目录里。

例如移动文件到 /usr/bin 目录,首先命令行进入其所在路径,然后将其移动到 /usr/bin:

1
sudo mv chromedriver /usr/bin

当然也可以将 ChromeDriver 配置到 $PATH,首先可以将可执行文件放到某一目录,目录可以任意选择,例如将当前可执行文件放在 /usr/local/chromedriver 目录下,接下来可以修改 ~/.profile 文件,命令如下:

1
export PATH="$PATH:/usr/local/chromedriver"

保存然后执行:

1
source ~/.profile

即可完成环境变量的添加。

验证安装

配置完成之后,就可以在命令行下直接执行 chromedriver 命令了。

命令行下输入:

1
chromedriver

输入控制台有类似输出,如图所示:

如果有类似输出则证明 ChromeDriver 的环境变量配置好了。

另外如果要配合代码进行测试的话,可以安装 Selenium,安装方式参考:https://setup.scrape.center/selenium,安装好了之后,随后再在程序中测试,执行如下 Python 代码:

1
2
from selenium import webdriver
browser = webdriver.Chrome()

运行之后会弹出一个空白的 Chrome 浏览器,证明所有的配置都没有问题,如果没有弹出,请检查之前的每一步的配置。

如果弹出之后闪退,则可能是 ChromeDriver 版本和 Chrome 版本不简容,请更换 ChromeDriver 版本。

如果没有问题,接下来我们就可以利用 Chrome 来做网页抓取了。

安装配置

pyquery 是一个强大的网页解析工具,它提供了和 jQuery 类似的语法来解析 HTML 文档,支持 CSS 选择器,使用非常方便,本节我们了解下它的安装方式。

相关链接

  • GitHub:https://github.com/gawel/pyquery
  • PyPi:https://pypi.python.org/pypi/pyquery
  • 官方文档:http://pyquery.readthedocs.io

安装方法

pip 安装

推荐使用 pip3 安装,命令如下:

1
pip3 install pyquery

命令执行完毕之后即可完成安装。

wheel 安装

当然也可以到 PyPi 下载对应的 wheel 文件安装,https://pypi.python.org/pypi/pyquery/#downloads,如当前最新版本为 1.2.17,则下载的文件名称为 pyquery-1.2.17-py2.py3-none-any.whl,下载到本地再 pip3 安装即可,命令如下:

1
pip3 install pyquery-1.2.17-py2.py3-none-any.whl

验证安装

安装完成之后,可以在 Python 命令行下测试。

1
2
$ python3
>>> import pyquery

如果没有错误报出,则证明库已经安装好了。

安装配置

在 Python3 中如果想要将数据存储到 MySQL 中就需要借助于 PyMySQL 来操作,本节我们介绍一下 PyMySQL 的安装方式。

相关链接

  • GitHub:https://github.com/PyMySQL/PyMySQL
  • 官方文档:http://pymysql.readthedocs.io/
  • PyPi:https://pypi.python.org/pypi/PyMySQL

安装方法

pip 安装

推荐使用 pip3 安装,命令如下:

1
pip3 install pymysql

执行完命令即可完成安装。

验证安装

为了验证库是否已经安装成功,可以在命令行下测试一下:

1
2
3
4
5
$ python3
>>> import pymysql
>>> pymysql.VERSION
(1, 0, 2, None)
>>>

在命令行首先输入 python3,进入命令行模式,输入如上内容,如果成功输出了其版本内容,那么证明 PyMySQL 成功安装。

安装配置

在 Python 中如果想要和 MongoDB 进行交互就需要借助于 PyMongo 库,本节我们来了解一下 PyMongo 的安装方法。

相关链接

  • GitHub:https://github.com/mongodb/mongo-python-driver
  • 官方文档:https://api.mongodb.com/python/current/
  • PyPi:https://pypi.python.org/pypi/pymongo

安装方法

pip 安装

推荐使用 pip3 安装,命令如下:

1
pip3 install "pymongo<4.0"

注意:因为 PyMongo 在 4.0 版本移除了 insert, update, remove 等方法,所以如果安装 4.0 及以上版本,《Python3网络爬虫开发实战(第二版)》书中部分代码会报错,3.x 就不会有问题。详情请见官方说明:https://pymongo.readthedocs.io/en/stable/migrate-to-pymongo4.html#collection-insert-is-removed

当然,如果安装 4.0 及以上版本的话也可以,需要手动把 insert 方法改成 insert_one 或 insert_many 方法即可。

运行完毕之后即可完成 PyMongo 的安装。

验证安装

为了验证库是否已经安装成功,可以在命令行下测试一下:

1
2
3
4
5
$ python3
>>> import pymongo
>>> pymongo.version
'3.11.2'
>>>

在命令行首先输入 python3,进入命令行模式,输入如上内容,如果成功输出了其版本内容,那么证明 PyMongo 成功安装。

安装配置

lxml 是 Python 的一个解析库,支持 HTML 和 XML 的解析,支持 XPath 解析方式,而且解析效率非常高,本节我们了解下它的安装方式,分为 Windows、Linux、Mac 三大平台来介绍。

相关链接

  • 官方网站:http://lxml.de
  • GitHub:https://github.com/lxml/lxml
  • PyPi:https://pypi.python.org/pypi/lxml

安装方法

Windows 下的安装

Windows 下可以先尝试利用 pip3 安装,直接执行如下命令即可:

1
pip3 install lxml

如果没有任何报错则证明安装成功。

如果出现报错,比如提示缺少 libxml2 库等信息,可以采用 wheel 方式安装。

推荐直接到这里下载对应的 wheel 文件,链接为:http://www.lfd.uci.edu/~gohlke/pythonlibs/#lxml,找到本地安装 Python 版本和系统对应的 lxml 版本,例如 Windows 64 位 Python3.6 就选择 lxml‑3.8.0‑cp36‑cp36m‑win_amd64.whl,以此类推,将其下载到本地。

然后利用 pip3 安装即可,命令如下:

1
pip3 install lxml‑3.8.0cp36cp36mwin_amd64.whl

这样我们就可以成功安装好 lxml 了。

Linux 下的安装

在 Linux 平台下安装问题不大,同样可以先尝试 pip3 安装,命令如下:

1
pip3 install lxml

如果报错,可以尝试下方的解决方案。

CentOS、RedHat

对于此类系统,报错主要是因为缺少必要的库。

执行如下命令安装所需的库即可:

1
2
sudo yum groupinstall -y development tools
sudo yum install -y epel-release libxslt-devel libxml2-devel openssl-devel

主要是 libxslt-devel libxml2-devel 这两个库,lxml 依赖于它们。安装好了之后重新尝试 Pip 安装即可。

Ubuntu、Debian、Deepin

报错的原因同样可能是缺少了必要的类库,执行如下命令安装:

1
sudo apt-get install -y python3-dev build-essential libssl-dev libffi-dev libxml2 libxml2-dev libxslt1-dev zlib1g-dev

安装好了之后重新尝试 pip 安装即可。

Mac 下的安装

在 Mac 平台下,仍然可以首先尝试 pip3 安装,命令如下:

1
pip3 install lxml

如果产生错误,可以执行如下命令将必要的类库安装:

1
xcode-select --install

之后再重新运行 pip3 安装就没有问题了。

lxml 是一个非常重要的库,比如 BeautifulSoup、Scrapy 框架都需要用到此库,所以请一定安装成功。

验证安装

安装完成之后,可以在 Python 命令行下测试。

1
2
$ python3
>>> import lxml

如果没有错误报出,则证明库已经安装好了。

安装配置

Flask 是一个轻量级的 Web 服务程序,简单、易用、灵活,在本书中我们主要用它来做一些 API 服务,本节我们来了解下它的安装方式。

相关链接

  • GitHub:https://github.com/pallets/flask
  • 官方文档:http://flask.pocoo.org
  • 中文文档:http://docs.jinkan.org/docs/flask
  • PyPi:https://pypi.python.org/pypi/Flask

pip 安装

推荐使用 pip 安装,命令如下:

1
pip3 install flask

运行完毕之后就可以完成安装。

验证安装

安装成功之后可以运行如下实例代码测试一下:

1
2
3
4
5
6
7
8
9
from flask import Flask
app = Flask(__name__)

@app.route("/")
def hello():
return "Hello World!"

if __name__ == "__main__":
app.run()

直接运行代码,可以发现系统会在 5000 端口开启 Web 服务,控制台输出如下:

1
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)

直接访问:http://127.0.0.1:5000/,可以观察到网页中呈现了 Hello World!,如图所示:

这样一个最简单的 Flask 程序就运行成功了。

安装配置

本节介绍下 Python 常用爬虫库 requests 的安装方式。

相关链接

  • GitHub:https://github.com/requests/requests
  • PyPy:https://pypi.python.org/pypi/requests
  • 官方文档:http://www.python-requests.org
  • 中文文档:http://docs.python-requests.org/zh_CN/latest

安装方法

pip 安装

无论是 Windows、Linux 还是 Mac,都可以通过 pip 这个包管理工具来安装。

在命令行下运行如下命令即可完成 requests 库的安装:

1
pip3 install requests

这是最简单的安装方式,推荐此种方法安装。

wheel 安装

wheel 是 Python 的一种安装包,其后缀为 whl,在网速较差的情况下可以选择下载下 wheel 文件再安装,直接用 pip3 命令加文件名安装即可。

不过在这之前需要先安装 wheel 库,安装命令如下:

1
pip3 install wheel

然后到 PyPi 上下载对应的 wheel 文件,如当前最新版本为 2.17.3,则打开:https://pypi.python.org/pypi/requests/2.17.3#downloads,下载 requests-2.17.3-py2.py3-none-any.whl 到本地。

随后命令行进入 Wheel 文件目录,利用 pip 安装即可。

1
pip3 install requests-2.17.3-py2.py3-none-any.whl

这样我们同样可以完成 requests 的安装。

源码安装

那么如果你不想用 pip 来安装,或者想获取某一特定版本,可以选择下载源码安装。

此种方式需要先找到此库的源码地址,然后下载下来再用命令安装。

requests 项目的地址是:https://github.com/kennethreitz/requests

可以通过 Git 来下载源代码:

1
git clone git://github.com/kennethreitz/requests.git

或通过 curl 下载:

1
curl -OL https://github.com/kennethreitz/requests/tarball/master

下载下来之后,进入目录,执行如下命令安装即可:

1
2
cd requests
python3 setup.py install

命令执行结束后即可完成 requests 的安装,由于此种安装方式比较繁琐,后文此种安装方式不再赘述。

验证安装

为了验证库是否已经安装成功,可以在命令行下测试一下:

1
2
$ python3
>>> import requests

在命令行首先输入 python3,进入命令行模式,然后输入如上内容,如果什么错误提示也没有,那么就证明我们已经成功安装了 requests。

安装配置

BeautifulSoup 是 Python 的一个 HTML 或 XML 的解析库,我们可以用它来方便地从网页中提取数据,它拥有强大的 API 和多样的解析方式,本节我们了解下它的安装方式。

相关链接

  • 官方文档:https://www.crummy.com/software/BeautifulSoup/bs4/doc
  • 中文文档:https://www.crummy.com/software/BeautifulSoup/bs4/doc.zh
  • PyPi:https://pypi.python.org/pypi/beautifulsoup4

准备工作

BeautifulSoup 的 HTML 和 XML 解析器是依赖于 lxml 库的,所以在此之前请确保已经成功安装好了 lxml 库,具体的安装方式参见 lxml 的安装:https://setup.scrape.center/lxml

安装方法

pip 安装

目前 BeautifulSoup 的最新版本是 4.x 版本,之前的版本已经停止开发了,推荐使用 pip3 来安装,安装命令如下:

1
pip3 install beautifulsoup4

命令执行完毕之后即可完成安装。

wheel 安装

当然也可以从 PyPi 下载 wheel 文件安装,链接如下:
https://pypi.python.org/pypi/beautifulsoup4

然后 pip3 安装 Wheel 文件即可。

验证安装

安装完成之后可以运行下方的代码验证一下。

1
2
3
from bs4 import BeautifulSoup
soup = BeautifulSoup('<p>Hello</p>', 'lxml')
print(soup.p.string)

运行结果:

1
Hello

如果运行结果一致则证明安装成功。

注意在这里我们虽然安装的是 beautifulsoup4 这个包,但是在引入的时候是引入的 bs4,这是因为这个包源代码本身的库文件夹名称就是 bs4,所以安装完成之后,这个库文件夹就被移入到我们本机 Python3 的 lib 库里,所以识别到的库文件名称就叫做 bs4,所以我们引入的时候就引入 bs4 这个包。

因此,包本身的名称和我们使用时导入的包的名称并不一定是一致的。

安装配置

之前我们介绍的 requests 库是一个阻塞式 HTTP 请求库,当我们发出一个请求后,程序会一直等待服务器的响应,直到得到响应后程序才会进行下一步的处理,其实这个过程是比较耗费资源的。如果程序可以在这个等待过程中做一些其他的事情,如进行请求的调度、响应的处理等等,那么爬取效率一定会大大提高。

Aiohttp 就是这样一个提供异步 Web 服务的库,从 Python3.5 版本开始,Python 中加入了 async/await 关键字,使得回调的写法更加直观和人性化,Aiohttp 的异步操作借助于 async/await 关键字写法变得更加简洁,架构更加清晰。使用异步请求库来进行数据抓取会大大提高效率,下面我们来看一下这个库的安装方法。

相关链接

  • 官方文档:http://aiohttp.readthedocs.io/en/stable
  • GitHub:https://github.com/aio-libs/aiohttp
  • PyPi:https://pypi.python.org/pypi/aiohttp

pip 安装

推荐使用 pip 安装,命令如下:

1
pip3 install aiohttp

另外官方还推荐安装如下两个库,一个是字符编码检测库 cchardet,另一个是加速 DNS 解析库 aiodns,安装命令如下:

1
pip3 install cchardet aiodns

测试安装

安装完成之后,可以在 Python 命令行下测试。

1
2
$ python3
>>> import aiohttp

如果没有错误报出,则证明库已经安装好了。

Python

2022 年最新 Python3 网络爬虫教程

大家好,我是崔庆才,由于爬虫技术不断迭代升级,一些旧的教程已经过时、案例已经过期,最前沿的爬虫技术比如异步、JavaScript 逆向、安卓逆向、智能解析、WebAssembly、大规模分布式、Kubernetes 等技术层出不穷,我最近新出了一套最新最全面的 Python3 网络爬虫系列教程。

博主自荐:截止 2022 年,可以将最前沿最全面的爬虫技术都涵盖的教程,如异步、JavaScript 逆向、安卓逆向、智能解析、WebAssembly、大规模分布式、Kubernetes 等,市面上目前就这一套了。

最新教程对旧的爬虫技术内容进行了全面更新,搭建了全新的案例平台进行全面讲解,保证案例稳定有效不过期。

教程请移步:

【2022 版】Python3 网络爬虫学习教程

本节会介绍 Windows、Linux、Mac 三大平台下安装 Python3 的过程。

相关链接

  • 官方网站:http://python.org
  • 下载地址:https://www.python.org/downloads
  • 第三方库:https://pypi.python.org/pypi
  • 官方文档:https://docs.python.org/3
  • 中文教程:http://www.runoob.com/python3/python3-tutorial.html
  • Awesome Python:https://github.com/vinta/awesome-python
  • Awesome Python 中文版:https://github.com/jobbole/awesome-python-cn

Windows 下的安装

Windows 下安装 Python3 的方式有两种,一种是通过 Anaconda 安装,Anaconda 提供了 Python 的科学计算环境,里面自带了 Python 以及常用的库,如果选用了此种方式后面的环境配置方式会更加简便,另一种是直接下载安装包安装,即标准的安装方式。下面会依次介绍这两种安装方式,任选其一即可。

Anaconda 安装

Anaconda 的 官方下载链接为:https://www.anaconda.com/products/individual,选择 Windows 版本的安装包下载即可,如图所示:

image-20211002155805295

如果下载速度过慢可以选择使用清华大学镜像,下载列表链接为:https://mirrors.tuna.tsinghua.edu.cn/anaconda/archive/,使用说明链接为:https://mirrors.tuna.tsinghua.edu.cn/help/anaconda/,可以选择需要的版本进行下载,速度相比官网会快很多。

下载完成之后直接双击运行安装包安装即可,安装完成之后 Python3 的环境就配置好了。

安装包安装

推荐直接下载可执行文件安装包安装,到官方网站下载 Python3 安装包。

https://www.python.org/downloads/

到目前为止,Python 的最新版本是 3.9.7,如果想安装特定的 Python 版本,可以查看网页下方的内容,看到各个 Python 安装包的发布历史,如图所示:

image-20211002160003693

由于一些库的兼容性问题,个人比较推荐安装 Python 3.6 或 3.7 版本,而非 3.8、3.9 版本。

Python 3.6 版本安装包的下载链接为:https://www.python.org/downloads/release/python-368/,Python 3.7 版本安装包的下载链接为:https://www.python.org/downloads/release/python-379/,打开之后可以看到如下下载列表:

image-20211002160607143

64 位系统可以下载 Windows x86-64 executable installer,32 位系统可以下载 Windows x86 executable installer。

下载完成之后,直接双击运行 Python 安装包,使用图形界面安装,设置好 Python 的安装路径,完成后将 Python3 和 Python3 的 Scripts 目录配置到环境变量即可。

环境变量的配置,此处以 Win10 系统为例进行演示。

假如我安装后的 Python3 路径为 C:\Python36,从资源管理器中打开该路径,如图所示:

将该路径复制下来。

随后打开电脑-属性,如图所示:

点击左侧的高级系统设置,即可看到在弹出的窗口中下方有环境变量按钮,如图所示:

点击环境变量,找到系统变量下的 Path 变量,随后点击编辑按钮,如图所示:

随后点击新建,新建一个条目,将刚才拷贝的 C:\Python36 复制进去,当然此处的路径就是你的 Python3 安装目录,请自行替换,然后再把 C:\Python36\Scripts 路径复制进去,如图所示:

最后点击确定即可完成环境变量的配置。

配置好环境变量之后,我们就可以直接在命令行直接执行环境变量路径下的可执行文件了,如 python、pip 等命令。

添加别名

以上两种安装方式任选其一即可完成安装,但如果我们之前安装过 Python2 的话,可能会导致版本冲突问题,比如在命令行下输入 python 就不知道是调用的 Python2 还是 Python3 了,为了解决这个问题,建议将安装目录中的 python.exe 复制一份,命名为 python3.exe,这样便可以调用 python3 命令了,实际上和 python 命令是完全一致的,这样可以更好地区分 Python 版本,当然如果没有安装过 Python2 的话也建议添加此别名,添加完毕之后如图所示:

对于 pip 来说,安装包中自带了 pip3.exe 可执行文件,我们也可以直接使用 pip3 命令,无需额外配置。

测试验证

安装完成之后我们可以通过命令行测试一下安装是否成功,在开始菜单搜索 cmd,找到命令提示符,就进入了命令行模式,输入 python 测试一下能否成功调用 python,如果添加了别名的话可以输入 python3 测试,在这里输入的是 python3,如图所示:

类似输出结果如下:

1
2
3
4
5
6
7
8
$ python3
Python 3.6.1 (v3.6.1:69c0db5, Mar 21 2017, 17:54:52) [MSC v.1900 32 bit (Intel)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> print('Hello World')
Hello World
>>> exit()
$ pip3 -V
pip 9.0.1 from c:\python36\lib\site-packages (python 3.6)

如果出现类似上面的提示,则证明 Python3 和 pip3 均安装成功,如果提示命令不存在,那么请检查下环境变量的配置情况。

Linux 下的安装

Linux 下安装方式有多种,命令安装、源码安装、Anaconda 安装。

使用源码安装需要自行编译,时间较长。推荐使用系统自带命令或 Anaconda 安装,简单高效。在这里对各种安装方式分别予以讲解。

命令行安装

不同的 Linux 发行版本安装方式又有不同,在此分别予以介绍。

CentOS、RedHat

如果是 CentOS 或 RedHat 版本,使用 yum 命令安装即可。

1
2
sudo yum update -y
sudo yum install -y python3

Ubuntu、Debian、Deepin

首先安装 Python3,使用 apt-get 安装即可,在安装前还需安装一些基础库,命令如下:

1
2
sudo apt-get install -y python3-dev build-essential libssl-dev libffi-dev libxml2 libxml2-dev libxslt1-dev zlib1g-dev libcurl4-openssl-dev
sudo apt-get install -y python3

执行完如上命令之后就可以成功安装好 Python3 了。

然后还需要安装 pip3,仍然使用 apt-get 安装即可,命令如下:

1
sudo apt-get install -y python3-pip

执行完毕之后便可以成功安装 Python3 及 pip3。

源码安装

如果命令行安装方式有问题,还可以下载 Python3 源码进行安装。

源码下载地址为:https://www.python.org/ftp/python/,可以自行选用想要的版本进行安装,在此以 Python3.6.2 为例进行说明,安装路径设置为 /usr/local/python3。

首先创建安装目录,命令如下:

1
sudo mkdir /usr/local/python3

随后下载安装包并解压进入,命令如下:

1
2
3
wget --no-check-certificate https://www.python.org/ftp/python/3.6.2/Python-3.6.2.tgz
tar -xzvf Python-3.6.2.tgz
cd Python-3.6.2

接下来编译安装,所需时间可能较长,请耐心等待,命令如下:

1
2
3
sudo ./configure --prefix=/usr/local/python3
sudo make
sudo make install

安装完成之后创建 Python3 链接,命令如下:

1
sudo ln -s /usr/local/python3/bin/python3 /usr/bin/python3

随后下载 Pip 安装包并安装,命令如下:

1
2
3
4
wget --no-check-certificate https://github.com/pypa/pip/archive/9.0.1.tar.gz
tar -xzvf 9.0.1.tar.gz
cd pip-9.0.1
python3 setup.py install

安装完成后再创建 Pip3 链接,命令如下:

1
sudo ln -s /usr/local/python3/bin/pip /usr/bin/pip3

这样就成功安装好了 Python3 及 pip3。

Anaconda 安装

Anaconda 同样支持 Linux,Anaconda 的官方下载链接为:https://www.anaconda.com/products/individual,选择对应版本的安装包下载即可。如果下载速度过慢同样可以使用清华镜像,参考 Windows 部分的介绍,在此不再赘述。

测试验证

命令行测试 Python3 和 pip3 是否安装成功。

1
2
3
4
5
6
$ python3
Python 3.6.2 (default, Nov 17 2017, 17:05:23)
Type "help", "copyright", "credits" or "license" for more information.
>>> exit()
$ pip3 -V
pip 8.1.1 from /usr/lib/python3/dist-packages (python 3.6)

如出现类似上面的提示,则证明 Python3 和 pip3 安装成功。

Mac 下的安装

在 Mac 下同样有多种安装方式,如 Homebrew、安装包安装、Anaconda 安装等,推荐使用 Homebrew 安装。

Homebrew 安装

Homebrew 是 Mac 平台下强大的包管理工具,首先安装 Homebrew,官方网站是:https://brew.sh/

执行如下命令即可安装 Homebrew:

1
ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"

安装完成后便可以使用 brew 命令安装 Python3 和 pip3 了。

如果要安装 Python3 的最新版本,可以直接运行如下命令:

1
brew install python3

由于一些库的兼容性问题,个人比较推荐安装 Python 3.6 或 3.7 版本,而非 3.8、3.9 版本。

所以这里如果要安装 Python3.7 版本,可以运行如下命令:

1
brew install python@3.7

命令执行完成之后发现 Python3 和 pip3 均已经安装成功。

安装包安装

可以到官方网站下载 Python3 安装包。链接为:

由于一些库的兼容性问题,个人比较推荐安装 Python 3.6 或 3.7 版本,而非 3.8、3.9 版本。

Python 3.6 版本安装包的下载链接为:https://www.python.org/downloads/release/python-368/,Python 3.7 版本安装包的下载链接为:https://www.python.org/downloads/release/python-379/,打开之后可以看到如下下载列表:

可以选择下载 Mac OS X 64-bit installer,下载完成之后打开安装包按照提示安装即可,安装完毕之后 Python 相关的环境变量就配置好了。

Anaconda 安装

Anaconda 同样支持 Mac,Anaconda 的官方下载链接为:https://www.anaconda.com/products/individual,选择 Python3 版本的安装包下载即可,如图所示:

如果下载速度过慢同样可以使用清华镜像,参考 Windows 部分的介绍,在此不再赘述。

测试验证

命令行测试 Python3 和 pip3 是否安装成功。

1
2
3
4
5
6
$ python3
Python 3.6.2 (default, Nov 17 2017, 17:05:23)
Type "help", "copyright", "credits" or "license" for more information.
>>> exit()
$ pip3 -V
pip 8.1.1 from /usr/lib/python3/dist-packages (python 3.6)

如出现类似上面的提示,则证明 Python3 和 pip3 安装成功。

结语

本节介绍了三大平台 Windows、Linux、Mac 下 Python3 的安装方式,如有疑问欢迎留言。

个人日记

今天搜歌的时候,发现因为版权问题,网易云和 QQ 音乐都搜不到,说酷狗上能搜到,于是我就把之前好久不用的酷狗音乐下载回来了。

不太记得从什么时候开始用网易云音乐听歌的了,好像是从大学的时候,自从用了网易云之后,酷狗就被我渐渐抛弃了。因为好久不用了,我都不太记得我的账号密码是什么了。

但抱着试一试的心态,我尝试登录了一下。

结果居然登录成功了!

然后我就发现了我好久之前收藏过的一些歌,这里截个图啦:

图里面的这个歌单是 14 年创建的,当时我还记得当时听酷狗音乐的时候,我喜欢玩 QQ 飞车这个游戏,喜欢边听歌边开赛车,为了速度与激情,当时我还专门搜集了好多比较动感的音乐,具体名字其实我也不关心,其实到现在我也分不清哪首歌究竟是谁唱的或者或歌名叫什么。但当时觉得就是好听,一气搜集了好多。

那个时候,我还很喜欢听河图、许嵩、汪苏泷、徐良等人的歌,暴露了哈哈哈,当时我还专门为他们创建了歌单。另外当时还有一些网络情歌、dj 土嗨歌也感觉不错,所以当时就加入到我的收藏里面了。

于是今天我就在挨个听我之前收藏的音乐,感觉每一首歌都是那么亲切!而且个个都是我喜欢的(毕竟是之前收藏过的)!有的歌,听的时候都勾起了我当时玩飞车驰骋赛场时候的画面,那种感觉,真的好亲切而又舒服。

不知道这个怎么解释,这种感觉,就像出门在外好久之后回到家的感觉,许久未见的老友见面的感觉,这些歌里面承载着满满的青春和回忆。

我不知道大家有没有之前也用过其他的一些音乐软件,比如说你也可能前几年用过酷狗、酷我、QQ 音乐、网易云,但是现在已经换了其他的音乐软件了。

如果是这样的话,我推荐大家尝试着找一找之前自己用过的音乐软件的账号,把它下载回来,听听自己之前收藏过的歌单,每个人都有每个人的青春和故事,听着自己喜欢过的老歌,一定是一番别样的感觉。

更多精彩内容,请关注我的公众号「进击的 Coder」和「崔庆才丨静觅」。

HTML

我对设计美学有一定执念,所以我在编写一些 UI 的时候会比较注重它的一些美观度、舒适度。

随着现在前端的发展,一些成套的组件库也是层出不穷了,比如基于 Vue 的 Element UI、iView,基于 React 的 Ant Design、Fluent UI 等等,它们设计其实挺好的,但有一些色彩搭配和风格还没有达到我理想中的样子。

我其实比较欣赏苹果的那种半透明设计风,比如这样的:

大家感兴趣可以看苹果的设计手册:https://developer.apple.com/design/human-interface-guidelines/

同时我也对 Material Design 中的一些光影理念比较推崇,但并不太喜欢原生的 Material Design,比如这样的:

我个人觉得这种原生的 Material Design 有点用力过猛,显得有些沉重。

相比之下,目前的一些 Element UI、Ant Design 则算是吸收了二者的一些优点,提供了一些 UI 组件,比如这样的:

这种设计风格显得没有 Material Design 那样厚重,也吸取了一些 Apple Design 的风格,另外一些光影效果拿捏也挺不错的。

然而,这些设计还是没有达到我理想中的样子。

比如说,卡片的一些设计、边缘轮廓的一些设计,总让我感觉没有那么清爽。

后来,直到我用了一款梯🪜子软件,它是基于一个叫做 STISLA 的 UI 框架设计的,整体风格是这样的:

一眼看过去,爱了爱了,看起来,它借鉴了 Material Design 的一些光影设计理念,同时又不显得那么厚重,一些布局、卡片和文本框的轮廓也显得很明晰,另外一些配色、图标的样式整体也给人一种清爽的感觉,总之我个人非常喜欢。

这个 UI 框架的名字叫做 STISLA,其官网是: https://getstisla.com/,它是基于 BootStrap4 编写的,很可惜的是,它现在没有提供 Vue、React 的支持,所以使用起来暂时还不能完全组件化,不过里面的一些 class 可以直接拿来用,就像使用 tailwind 一样。

STISLA 现在是完全开源的,其 GitHub 仓库是:https://github.com/stisla/stisla,使用的话直接引用其中的 CSS 即可,这里就不再赘述了。

另外关于更多的组件,大家可以到官方 Demo 示例页面来体验,链接是:https://demo.getstisla.com/index.html,这里简单列举几个组件。

头像:

文章列表:

统计图表:

用户信息:

表单:

表格:

不得不说,每个版面我都觉得非常赏心悦目!感兴趣的就去官网看看吧。

好啦,以上就介绍这么多啦,大家感兴趣的话就快快用起来吧~

更多精彩内容,请关注我的公众号「进击的 Coder」和「崔庆才丨静觅」。

个人日记

最近看了一些书,学到了一些知识和方法论,比如说看刘未鹏的《暗时间》,就学到了很多知识。

读完之后,收获非常大,于是我决定把其中的一些感想写出来。

首先自然而然的就是有关暗时间的解释了,比如什么是暗时间,怎么来利用暗时间,就是我前面发的那篇文章。

虽然整体上看起来这篇文章的中心思想非常简单,我当时看书的时候也体会到了其中的一些中心思想,但等我真的要把这个写成一篇文章或者感想的时候,我却发现我却想不出一个好的定义,好的引入的例子,我原本以为我能非常流畅地把这篇文章写下来的,但真的到写的时候才发现我并没有做好准备,又去翻看了一下原书才写出来。

通过这件事,我就反思到了两件事:

  • 我需要利用好自己的暗时间来思考。

  • 读一本书,不去复盘和思考,收效甚微。

对于第一点。比如我在写文章之前,我应该先构思好我这篇文章的整体脉络,比如怎么引入、怎么说明、怎么结尾等等,这些思考其实平常就可以做的,比如我在下班路上、我在洗漱的时候、我在吃饭的时候,就完全可以拿这个时间来思考,思考不出来的,记下来再去翻看下,看看人家作者是怎么把这个观点表达出来的,作者的思路是怎样的。只有思考过了,脑中有大致的脉络,到时候真的写的时候就变得非常流畅了。而如果平时不加思考,只有写作的时候才去思考,有时候写作效率就会比较低。虽然写作也能帮助很好地思考,真的写不出来的时候再去构思和翻看未尝不可,但总体来说,这样不会比平时也思考的结果来的更好。

对于第二点。有时候我看完一本书,不去复盘和思考,只是泛泛地看完,看完就丢在一边再也不打开,其实收效是非常小的。一本书,往往是一个人几年甚至几十年的精华总结,组织很调理而且完整。相比网上一些碎片化的文章、博客、问答,一本书的价值简直大太多了。读书,自然就要理会到其中作者为我们传达的知识、思想,我们不是为了读书而读书,读书是和作者在心灵层次上的交流,要真的理会到作者的传达的意思,我们往往是需要多去思考、多去复盘、多去实践的。因为,这些知识的形成,也都是作者历经了很多实践和思考换来的,如果我们只是浮光掠影地过一遍,不多去思考和复盘,收效往往并不大。

所以说,这件事让我体会到了这两点。不管是做什么吧,多去思考、复盘,同时要利用好自己的暗时间,结果往往总会是不错的。

共勉。

更多精彩内容,请关注我的公众号「进击的 Coder」和「崔庆才丨静觅」。

个人日记

最近看了一本书叫《暗时间》。一开始我不知道这本书为什么叫这个名字,什么是暗时间啊?

翻开这本书才知道,这本书算是作者刘未鹏的个人方法论总结,其中有一个就叫做暗时间,这也是相当重要的一个部分,读完这部分之后我来写写自己的一些感想。

首先就说说暗时间吧,暗时间什么意思呢?我们先举几个例子来体会下。

比如说一个人,学习看起来非常刻苦,早上不到七点就去自习室了,然后直到晚上熄灯才回来。可是你会发现他考试成绩还是很差。这除了一些智力影响,还有一个重要的影响就是对时间和效率的把控。虽然有的人看起来非常刻苦,但有时候你会发现,他可能人坐在那里,但思绪却已经飞到了九霄云外。有些人学习但时候把一些定义、方法读了又读,但并没有真正地去思考和理解,其实最后还是没有把一件知识想透彻想明白。总的来说,这些有效的时间都没有用来真正去做有效率的事情。

比如说一台服务器,配置相当牛逼,不管是内存、CPU、GPU 都是顶配中的顶配,但是却被放在那闲置不用,一点功能也没有发挥出来,这资源就白白浪费掉了。但如果我们把它每天进行一些数据处理、计算,发挥出它的最大价值,这才是这正利用好了这些时间和资源。所以,这台服务器闲置是一天,满载运行计算也是一天,这两种情况,对服务器来说,产生的价值是完全不一样的。

每个人每天都有 24 小时,但不同的人,利用这 24 小时的方式不同,那产生的效果也就是不同的。人的一天中,有睡眠时间、通勤时间、工作时间、娱乐时间还有很多碎片时间,在这些时间里面,其实有很多时间是暗时间,有的人利用了这些时间进行了思考和学习,而有的人却在这个时间无所事事。

书中特别强调了思考的重要性,一个人在想什么,我们其实是看不出来的,但同时思考又是非常重要的。善于利用暗时间进行思考的人,可以无形中比别人多出好多的时间,从而实际意义上会比别人或好多年。

书中有一个比喻我特别喜欢:

我们每一个人的生命都像一个沙漏,里面装的沙子都差不多(因为大家的寿命都差不多),但不同的是,有些人的沙漏颈部较细,有些人的沙漏颈部较粗。颈部较细的沙漏可以抓住每一粒时间只沙,虽然沙子总量是一样的,但却会拥有更长的生命。

我觉得这个写的是太好了。

我们做一件事情,不能单纯地以投入的时间作为评判标准,时间和效率的乘积才是真正有意义的衡量标准。比如说背个英语单词吧,虽然我们一直在朗读,但发现记忆效果就是差,这是因为很多时间我们并没有去投入身心去记忆和思考,去思考这个单词更深层次的用法,和其他单词、场景的联系等等。同样学习一些新的知识,如果我们不去深入思考,也只能浅尝辄止。

学习就像电脑的 CPU 处理程序一样,如果我们始终给一个任务分配高的优先级,分配更多的时间到上面,那效果自然就是更好一些。学习知识也是一样,如果我们能够始终多去思考,去思考知识底层更深层的原理和关联,那效果也会更好。

但,人的时间总是有限的,比如工作的时间是有限的,学习的时间也是有限的,也有一些时间必须要用来做固定的事情,比如通勤、吃饭等等。但有时候我们就发现,有些人似乎看的东西不我们你多,玩的也不比我们少,但不知道为什么别人就是比我们走的更远。有一个诀窍就是利用了一些暗时间,这部分暗时间完全可以用来思考。

比如说走路、买菜、洗漱、通勤、出游、吃饭等等,这些时间都可以看作是暗时间,我们可以充分利用这些时间进行思考,梳理和精细化之前看的和读的东西。慢慢地,时间长了,就会产生巨大的影响。

平时生活中,我们可以在大脑中时刻留着几个问题,比如工作上的、生活上的、学习上的都可以,如果记不住的话,那就可以把一些可以利用空闲时间思考的问题记录到一个备忘录里面,有事没事的时候翻出来就思考一下。比如说坐车的时候,我们可以去思考今天遇到的一个算法或设计上的问题。比如说洗漱的时候,我们可以构思一篇文章或者文档的内容和设计。有时候可能洗漱的时候,灵感突然来了,一个问题就马上被想通了。这就是充分利用了暗时间的效果。

能把握住暗时间的人,其实就相当于无形中拥有了更多的时间,这些时间被用在了思考之上,也就可以走的更远。

好嘞,就先写到这里,大家也可以试试看,让我们把握住自己的时间沙漏,利用好我们的暗时间,相信它会迸发出意想不到的威力的。

更多精彩内容,请关注我的公众号「进击的 Coder」和「崔庆才丨静觅」。

技术杂谈

有时候我们在逛博客、技术帖子的时候会发现有人是这么分享代码的?

这其实是一张图片,虽然说里面的内容不能复制,但是这张图片整体看看起来就很精致有没有?左上角的三个红黄绿的按钮,就是 Mac 中的窗口操作按钮,然后代码的高亮配色都和我们 IDE 中的配色是完全一致的,整体就是一个 Mac 的风格。

最近群里也有小伙伴在问这个是怎么做的,这里就来给大家介绍一下。

这个工具叫做 carbon,它有自己的网站、代码仓库,另外还有一个类似的VS Code 插件,这里统一给大家介绍下。

网站

carbon 自己维护了一个网站:https://carbon.now.sh/,我们可以打开它来看看:

可以看到 carbon 提供了多种选项,比如主题选择、编程语言、还有一些底色、间距的配置,下方就是代码的预览效果,同时我们还可以在这里编辑代码。

我们可以在下方任意贴上我们想要贴的代码,比如这里有一段 Python 代码:

1
2
3
4
def get_vowels(string):
return [vowel for vowel in string if vowel in 'aeiou']

print("Vowels are:", get_vowels('This is some random string'))

我们可以直接贴进去,然后我们可以选择喜欢的主题配色,语言,同时可以选择背景颜色,还可以点击 setting 的按钮配置更详细的内容:

这里的 Window controls 可以控制左上角的现实效果,比如是否带有圆角效果、按钮是否带有颜色,另外还可以控制内边距、阴影等等,还有一些自动调整宽度的配置。

完事之后直接点击复制,这里有几个选项,比如复制图片、URL 或者直接复制 iframe。

比如点击复制为 iframe 链接就会得到如下内容:

1
2
3
4
5
<iframe
src="https://carbon.now.sh/embed?bg=rgba%28255%2C255%2C255%2C1%29&t=material&wt=none&l=python&ds=true&dsyoff=20px&dsblur=68px&wc=true&wa=true&pv=0px&ph=0px&ln=false&fl=1&fm=Hack&fs=14px&lh=133%25&si=false&es=2x&wm=false&code=def%2520get_vowels%28string%29%253A%250A%2520%2520%2520%2520return%2520%255Bvowel%2520for%2520vowel%2520in%2520string%2520if%2520vowel%2520in%2520%27aeiou%27%255D%2520%250A%250Aprint%28%2522Vowels%2520are%253A%2522%252C%2520get_vowels%28%27This%2520is%2520some%2520random%2520string%27%29%29"
style="width: 561px; height: 146px; border:0; transform: scale(1); overflow:hidden;"
sandbox="allow-scripts allow-same-origin">
</iframe>

复制为 URL 就会直接得到一个可供使用的 URL:

1
https://carbon.now.sh/?bg=rgba%28255%2C255%2C255%2C1%29&t=material&wt=none&l=python&ds=true&dsyoff=20px&dsblur=68px&wc=true&wa=true&pv=0px&ph=0px&ln=false&fl=1&fm=Hack&fs=14px&lh=133%25&si=false&es=2x&wm=false&code=def%2520get_vowels%28string%29%253A%250A%2520%2520%2520%2520return%2520%255Bvowel%2520for%2520vowel%2520in%2520string%2520if%2520vowel%2520in%2520%27aeiou%27%255D%2520%250A%250Aprint%28%2522Vowels%2520are%253A%2522%252C%2520get_vowels%28%27This%2520is%2520some%2520random%2520string%27%29%29

有了 URL 之后我们可以到任意地址引用。

当然导出也是,可以选择导出矢量图 svg 还是 png,如图所示:

我的话一般都是点击复制,然后用我的 ipic 工具上传到 CDN 上面。

仓库

同时 carbon 还是开源的,GitHub 仓库地址是:https://github.com/carbon-app/carbon,其实这就是刚才我们看到的网站的源码,是基于 Node.js 开发的,扒一扒 package.json源码:

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
31
32
33
34
35
36
37
38
"dependencies": {
"@next/bundle-analyzer": "^10.2.2",
"@reach/visually-hidden": "^0.15.0",
"actionsack": "^0.0.14",
"axios": "^0.21.1",
"cm-show-invisibles": "^3.1.0",
"codemirror": "5.61.1",
"codemirror-graphql": "^1.0.1",
"codemirror-mode-elixir": "^1.1.2",
"codemirror-solidity": "^0.2.3",
"date-fns": "^2.21.3",
"dom-to-image": "^2.6.0",
"downshift": "^6.1.3",
"dropperx": "^1.0.1",
"eitherx": "^1.0.2",
"email-validator": "^2.0.4",
"escape-goat": "^3.0.0",
"firebase": "^8.6.2",
"graphql": "^15.5.0",
"highlight.js": "^10.7.2",
"lodash.debounce": "^4.0.8",
"lodash.omitby": "^4.6.0",
"match-sorter": "^6.3.0",
"morphmorph": "^0.1.3",
"ms": "^2.1.3",
"next": "^10.2.2",
"next-offline": "^5.0.5",
"prettier": "^2.3.0",
"react": "^17.0.2",
"react-click-outside": "^3.0.0",
"react-codemirror2": "^7.2.1",
"react-color": "^2.19.3",
"react-dom": "^17.0.2",
"react-image-crop": "^6.0.16",
"react-mailchimp-subscribe": "^2.1.3",
"tohash": "^1.0.2",
"use-climate-change-reminder": "^0.0.7"
},

可以看到它主要基于 Next、React、CodeMirror 开发的,如果大家想基于此进行二次开发也可以,比如对接云存储,实现一些自动化等等。

如果觉得刚才的网站能够满足需求的话,那大可继续使用刚才的网站。

VS Code 插件

另外还有一个类似的 VS Code 插件,也可以实现类似的功能,我觉得还挺好用的,插件叫做 CodeSnap,现在已经十七万多次下载了。

大家在 VS Code 里面搜索生成就好了。

那怎么生成代码图片呢?

其实很简单,我们先选中想要分享的代码:

然后右键菜单选择 CodeSnap 即可,接着在 VS Code 面板就可以生成对应的代码预览效果了:

这时候我们可以点击上方的这个按钮,就可以直接把代码对应的图片下载下来了,如图所示:

得到的效果就和你自己编辑器里面看到的一样,是不是感觉很不错?

CodeSnap 也可以支持在 VSCode 里面更改 settings,目前支持如下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
codesnap.backgroundColor: The background color of the snippet's container. Can be any valid CSS color.

codesnap.boxShadow: The CSS box-shadow for the snippet. Can be any valid CSS box shadow.

codesnap.containerPadding: The padding for the snippet's container. Can be any valid CSS padding.

codesnap.roundedCorners: Boolean value to use rounded corners or square corners for the window.

codesnap.showWindowControls: Boolean value to show or hide OS X style window buttons.

codesnap.showWindowTitle: Boolean value to show or hide window title folder_name - file_name.

codesnap.showLineNumbers: Boolean value to show or hide line numbers.

codesnap.realLineNumbers: Boolean value to start from the real line number of the file instead of 1.

codesnap.transparentBackground: Boolean value to use a transparent background when taking the screenshot.

codesnap.target: Either container to take the screenshot with the container, or window to only take the window.

codesnap.shutterAction: Either save to save the screenshot into a file, or copy to copy the screenshot into the clipboard.

关于更多的内容大家可以直接到 CodeSnap 插件的主页查看:https://marketplace.visualstudio.com/items?itemName=adpyke.codesnap

另外这里也顺便提下我的 VS Code 主题,我用的是 Community Material Theme 这个插件:

然后使用它提供的第一个默认主题:

如果大家觉得不错的话也欢迎试试哈~

希望对大家有帮助。

更多精彩内容,请关注我的公众号「进击的 Coder」和「崔庆才丨静觅」。

技术杂谈

有时候我们可能为了测试各种各样的功能,需要用邮箱注册一些网站,然后过段时间就发现这个网站开始往我们的邮箱发送一些垃圾广告信息,比如如图所示:

image-20210725124859662

很多邮箱服务有自动垃圾邮件分类的功能,但免不了的还是有些不好区分,来了不感兴趣的邮件我们还是要手动设置下规则,有时候关还关不掉。

如果我们只是为了简单测试某些功能,同时又不想发生上面的事情,那我们其实可以用一些小号邮箱来注册,或者生成一些临时邮箱来注册。

这里来介绍一个工具,帮助快速生成临时邮箱,简单好用。

tmpmail

这个工具叫做 tmpmail,GitHub 地址是 https://github.com/sdushantha/tmpmail,它就是一个简单的命令行 Shell 脚本,安装完了之后就可以使用了,Windows、Linux、Mac 上都是可用的。

由于我使用的是 Mac,这里介绍下 Mac 的安装方式,首先安装依赖:

1
brew install w3m curl jq

然后根据 GitHub 对应的提示安装即可:

1
curl -L "https://git.io/tmpmail" > tmpmail && chmod +x tmpmail

我们也可以把它移动到对应的系统路径下,比如 /usr/bin,/usr/local/bin 等:

1
mv tmpmail /usr/local/bin/

这样就安装好了,tmpmail 就可以正常使用了。

使用

使用其实非常简单,首先我们使用 tmpmail 就可以生成一个临时邮箱:

1
tmpmail

运行结果类似如下:

1
2
3
[ Inbox for hl9dvc3wbub@yoggm.com ]

No new mail

这里其实就是生成了一个临时邮箱,叫做 hl9dvc3wbub@yoggm.com,然后下面提示了 No new mail,就是没有新邮件的意思。

有朋友就会说了,咋没有密码啊?其实我们无需关心密码的对不对,我们只关心它收到的邮件内容就好了,比如我们应该是拿着这个邮箱去别的网站注册账号,然后网站会往这个邮箱发送一封激活邮件,点击就激活了。所以对于这个临时邮箱,我们只要能知道邮箱里面收到的邮件就好了。

所以 tmpmail 其实相当于帮我们维护了密码,我们无需关心,它可以自动帮我们把收件箱里面的邮件列出来。

OK,如果我们要更换邮箱也可以,直接重新生成一个就好了:

1
tmpmail --generate

运行结果如下:

1
j2uabw3jmfn@wwjmp.com

这样就重新生成了一个新的邮箱。

这时候重新运行 tmpmail,它就会使用当前最新生成的邮箱,运行结果如下:

1
2
3
[ Inbox for j2uabw3jmfn@wwjmp.com ]

No new mail

OK,准备工作就绪。

注册测试

接下来我们就随便找个网站注册个账号试试吧。

比如 Zyte 这个平台,前身叫做 Scrapinghub,提供一些数据爬取的服务,网址是 https://www.zyte.com/,如图所示:

我们来注册下试试:

填上用户名之后,我们使用刚才 tmpmail 生成的临时邮箱来注册这个账号,输入密码之后点击注册。

这里 Zyte 就提示我们激活邮件就发送出去了,我们需要到对应邮箱里面查收激活链接并激活账号。

查收激活

OK,回到 tmpmail,看看邮件收到没。

还是输入:

1
tmpmail

这时候就可以看到如下运行结果了:

1
2
3
[ Inbox for j2uabw3jmfn@wwjmp.com ]

231112827 bounce+6ec610.a13e529-j2uabw3jmfn=wwjmp.com@mg.zyte.com Zyte Email confirmation

非常赞,这里就显示了是 bounce+6ec610.a13e529-j2uabw3jmfn=wwjmp.com@mg.zyte.com 发送的邮件,邮件标题是 Zyte Email confirmation,然后最前面有个 ID,是 231112827。

P.S.:其实看着 Zyte 也是一个临时邮箱发送的这个邮件。

那这么打开这个邮件呢?很简单,tmpmail 命令加这个 ID 就好了:

1
tmpmail 231112827

运行结果如下:

这里就把邮件的内容展示出来了,内容就是感谢您的注册,然后点击链接激活即可,tmpmail 还自动解析了可点击的内容,比如超链接变成了可点击的内容,我们可以直接鼠标点击中间蓝色的 Confirm mail address 就在命令行下触发了链接的访问。

然后接下来命令行还显示了点击链接之后的网页内容:

基本上就是感谢您的注册,您的账户已经成功激活了。

完事了!

到现在为止我们就轻松利用 tmpmail 利用临时邮箱注册好了一个测试账号。

重新回到 Zyte 里面,输入刚才的邮箱和密码就成功登录进来了:

这里有代理服务、自动内容提取、云爬虫、Splash 渲染服务,大家感兴趣的话也可以试试看~

更多精彩内容,请关注我的公众号「进击的 Coder」和「崔庆才丨静觅」。

技术杂谈

事情是这样的,最近组里新建了一个代码仓库来开发一个新的产品,再加上今天北京下大雨很多同事选择在家工作(包括我也是),于是我就选择用自己的个人电脑来工作。

但我的个人电脑里面的 Git 信息是用的我自己的个人邮箱:

1
2
git config --global user.name "Germey"
git config --global user.email "cqc@cuiqingcai.com"

这两行命令大家用过 Git 的肯定都敲过对吧?

这个配置是全局生效的,所以如果我用 Git 的 commit 命令来提交代码的话,那么 commit 的名字和邮箱就会变成刚才我配置的个人信息。

然后如果把代码推送到公司的代码仓库里面,里面就会出现一个奇奇怪怪的用户名和头像,就像这样子:

图中上面两次 commit 就是我用个人电脑提交的,最后的那次 commit 是我上周在公司用公司电脑提交的。

这是不是很奇怪?

如果其他人也用的个人邮箱提交,那公司代码库里面就会出现各种怪怪的提交人的记录,无从知晓。

这肯定不能忍啊,以后要是有谁写了奇怪的代码都不好查是谁写的。

于是乎,我灵机一动,想:为何不在提交代码的时候做一个限制呢?

能做到吗?当然可以!

Git Hook

这里就介绍一个知识点 - Git Hook,它的意思就是在 Git 各种事件执行前和执行后执行一些自定义的逻辑,比如说,我们定义一个 pre-commit 的 Git Hook,那就能在 commit 之前执行一些操作,我们定义一个 post-push 的 Git Hook,那就能在 push 操作之后执行一些操作。

有关具体的内容可以参考官方文档:https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks

好,那这里我其实就是需要在 commit 之前做一下 Git 信息检查就好了,比如检查配置的邮箱不是工作邮箱,那就不允许执行 commit,所以就不会出现奇奇怪怪的 commit 记录了。

实操

说干就干。

配置 Git Hook 的工具有很多,Git 有原生支持,当然我们也可以用第三方库来做。

目前我们的代码仓库是基于 Node.js 开发的,所以 Node.js 的项目配置 Git Hook 比较流行的解决方案就是 husky,所以这里我也用 husky 来做了。

首先安装下 husky:

1
yarn add husky

然后配置一个 Node.js 的 prepare 命令,这个命令可以在装完包 Node.js 包之后自动执行,所以 prepare 命令就配置成 husky 初始化的脚本,package.json 里面增加如下配置:

1
2
3
4
5
6
{
"scripts": {
...
"prepare": "npx husky install"
},
}

OK,这样的话,其他人如果 clone 了这个仓库,装完所有 Node.js 包之后就会自动初始化 husky 的配置,然后在项目本地生成一个 .husky 的初始化目录,这样 Git Hook 就生效了。

Git Hook 生效之后,所有定义在 .husky 目录下的 Hook 脚本都会被应用,比如如果在 .husky 目录下添加一个 pre-commit 的脚本,那执行 commit 的之前,该脚本就会被预先执行做一些检查工作。

所以 .husky 目录下我就创建了一个 pre-commit 的脚本,写入了如下内容:

1
2
3
4
5
6
7
8
9
EMAIL=$(git config user.email)
if [[ ! $EMAIL =~ ^[.[:alnum:]]+@microsoft\.com$ ]];
then
echo "Your git information is not valid";
echo "Please run:"
echo ' git config --local user.name "<Your name in Microsoft>"'
echo ' git config --local user.email "<Your alias>@microsoft.com"'
exit 1;
fi;

这是一个 Linux Shell 脚本,完全遵循 Shell 语法。

这里其实就是获取了 git config user.email 的返回结果,然后用正则表达式匹配是否符合公司邮箱格式,比如我们公司邮箱后缀当然是 microsoft.com 后缀,所以这里就用了 ^[.[:alnum:]]+@microsoft\.com$ 来进行匹配了。这里值得注意的是,为什么这里没有用 \S 来代表非空白字符,而是用了一个 [:alnum] 呢?这是因为 Bash Shell 本身不支持 \S 这种匹配,所以这里得换成 [:alnum]

然后如果不匹配怎么办呢?

那就输出一些错误提示就好了,比如这里就提示请使用 git config —-local 命令来配置用户名和邮箱,之所以用-—local 是因为不想该配置影响全局的 Git 配置,所以这个配置只针对该仓库生效,然后 exit 1 就触发异常退出,程序运行终止,从而也不会触发 commit 命令了。

有了这个配置,我们来尝试下效果。

这会我没有做任何修改,Git 还是原来的配置,即我的全局个人邮箱配置。

这时候我执行下 commit 命令,就出现错误提示了:

1
2
3
4
Your git information is not valid
Please run:
git config --local user.name "<Your name in Microsoft>"
git config --local user.email "<alias>@microsoft.com"

很棒!检测出来了。

按照这个提示说的,然后我运行下配置命令:

1
2
git config --global user.name "Qingcai Cui"
git config --global user.email "xxxx@microsoft.com"

这里呢,我就配置了我的公司个人信息和公司邮箱。

然后重新再执行 commit 命令,就不会再出现如上的错误提示了!commit 成功!

大功告成!!!

有了它,我们就可以成功阻止一些奇奇怪怪的 commit 乱入公司的代码仓库了!

然后我把这个 PR 发出去了,有同事似乎也是深有感触,说道:

哈哈哈,有了这个,以后我们应该再也不会看到我们的代码仓库里面有 QQ 邮箱啦!

希望对大家有帮助~

更多精彩内容,请关注我的公众号「进击的 Coder」和「崔庆才丨静觅」。

个人随笔

想必大家都是从学生时代过来的,或者现在还处于学生时代。

在学生时代,大家有没有见过,有的同学非常非常努力,上课听得非常认真,笔记也记录得非常认真,同时各种颜色和标记把书上画得密密麻麻,但是似乎考试成绩并没有那么理想。然而有的同学,几乎也不记笔记,书上甚至几乎都是干干净净的,也不见有多么努力,但是考试成绩就是好。

这是为什么呢?最近看了《认知天性》这本书的一些内容,觉得说得还是很有道理的,在这里想把我的一些感悟写下来。

那些没有效果的努力

首先我们不能否认智力上确实有的同学就是聪明,这一点在数理化上体现得更为明显一些。但是在偏重一些记忆的科目上,上述的情况也有很多很多。比如同样记忆一些知识点,背英语单词等,有些同学非常努力地在大声反复背和念,但是效果并不理想,但有的同学其实就简单看几遍,然后后面就记得很牢。

说这个例子我想表明的一个观点是:耗费心血的学习才是深层次的,效果也会更持久,不花力气的学习就像在沙子上写字,今天写上,明天字就消失了。

之前老师经常教导我们,背单词要大声读出来,多读几遍,多拼几遍,手口耳并用。学习一个知识点的时候,我们要反复去看和阅读,并进行一些集中练习。但其实,这些并没有抓住本质,反而可能还是效率最低的学习方式。因为这个单词或知识点,我们只是在大声阅读、拼写而已,这种行为其实可以归为一种不花力气的学习,这个学习过程并没有给大脑带来什么挑战,这种记忆效率往往并不理想,而且就算当时记住了,那也会很快忘记了。

其实我之前其实也很多时候也用刚才说的模式来学习。比如背单词的时候我就反复拼好多遍、读好多遍,而很少主动去默写,去联想记忆,去做自测等等。所以,大部分时间,我的其实就是反复机械式地向大脑灌输一些强化的信息,最后的结果就是,很多单词和知识点,我当时从 0 到记忆住一个知识点的过程比较漫长,而且后来绝大多数知识点也都很快忘记了。

我印象深刻的知识是怎么来的

但我也回想起来,似乎之前学习过的某些知识点我到现在还记得,而且记得很牢。

比如我现在就记得几个地理知识点,亚洲和北美洲的分界线是白令海峡,北美洲和南美洲的分界线是巴拿马运河,等等。

为什么这几个知识点我记得这么牢呢?因为我当时在记忆这几个知识点的时候,我姐姐正好来了我家,我就让她给我提问这几个知识点。一开始提问的时候我其实并没有记录得多么牢,绞尽脑汁也没想出来,导致好几个问题答错了,错了之后我就去再记一遍,然后又让我姐姐给我提问。似乎经历了三四轮吧,最后我把所有的问题都答对了,于是后来我就对这些知识点的印象深刻很多。

正是因为这个过程,我的大脑经历了反复几次有挑战性的任务,在姐姐跟我提问的过程中,大脑触发了一些主动的思考和联想,然后没有记住的知识点再进行更有针对性的记忆。经过反复的几次提问,大脑的思考,知识的检索,这几个知识点就在我脑海中的印象变得更加深刻。

所以,耗费心血、对大脑有挑战性的学习才是深层次的,这个过程往往伴随着大脑对信息的主动检索,效果也会更好。

什么才是有效的学习

所以,在很多情况下,我们要进行一些有效的学习,就需要做一些对大脑有挑战性的事情,这里举几个例子方便大家理解:

  • 比如记忆一个知识点或者背单词的时候,反复阅读的效果往往是很差的,我们可以尝试让自己不看书本复述或默写出来,对大脑带来一些挑战,这样记忆一遍会更加深刻,另外让别人给自己提问或者默写也是不错的强化记忆的有效手段。

  • 比如读一本书,我们自以为是看完了,但其实本质上我们只是经历了一次泛读的过程,当时有个印象,但过段时间就几乎忘得差不多了。我们可以尝试拿出一张纸或者打卡一个空的思维导图,让大脑把整体的思维脉络、核心内容梳理出来。这当然是一个非常难的过程,我们可能很难一次性完成,但没关系,想不起来的就把书再打开看看,然后继续默写。多尝试两三次,我们会对整个书本的内容在大脑里面形成更深刻的印象。

  • 比如看了一篇讲座或者学习了一个课,最有效的学习和记忆手段往往是把读后感或者总结写出来,或者动手跟着做一遍,因为这个过程伴随着非常多的脉络梳理和大脑主动检索内容的过程。有人说,一个内容只看一遍,最后我们往往只能记住一小部分,但是如果我们能够把整个内容讲出来,那最后我们几乎能记住绝大部分。我也在做类似的尝试,比如今天我看了《认知天性》这本书的第一章内容,我就尝试把自己的读后感写出来,也就是你正看到的这篇文章,我相信我以后会对这部分内容的理解和记忆更加深刻。比如我学习了一个新的知识点,我就尝试把它梳理成自己的一篇文章,在梳理的过程中,我经历了思考、检索、记忆等各个过程,最后理解也会更加全面和深刻。

人天性是懒惰的,学习的过程中,大脑跟着一起懒惰,去机械式地学习往往是不可取的。所以,总的来说,我们在学习的时候不要死板地反复阅读,可以尽量给大脑一些有挑战性的工作,比如尝试默写、复述、思考和梳理等等,让大脑主动去检索和记忆,如果发现想不起来,那我们也知道哪部分内容需要重点学习的内容。只有这样,才是学习和记忆的正确方式。

共勉。

更多精彩内容,请关注我的公众号「进击的 Coder」和「崔庆才丨静觅」。

Python

之前我曾经写过一篇文章说 Google ReCAPTCHA 验证码的绕过方法,当时介绍的是用 2Captcha,然而有些朋友跟我反映说 2Captcha 价格比较贵,而且用起来比较复杂。

今天再给大家介绍另一个用于破解 Google ReCAPTCHA 的方法。

ReCAPTCHA 介绍

可能大家还没听说过什么是 ReCAPTCHA,可能由于某些原因,这个验证码在国内出现不多,不过想必大家应该多多少少见过或用过。它长这个样子:

这时候,只要我们点击最前面的复选框,验证码算法会首先利用其「风险分析引擎」做一次安全检测,如果直接检验通过的话,我们会直接得到如下的结果:

如果算法检测到当前系统存在风险,比如可能是陌生的网络环境,可能是模拟程序,会需要做二次校验。它会进一步弹出类似如下的内容:

比如上面这张图,验证码页面会出现九张图片,同时最上方出现文字「树木」,我们需要点选下方九张图中出现「树木」的图片,点选完成之后,可能还会出现几张新的图片,我们需要再次完成点选,最后点击「验证」按钮即可完成验证。 或者我们可以点击下方的「耳机」图标,这时候会切换到听写模式,验证码会变成这样:

这时候我们如果能填写对验证码读的音频内容,同样可以通过验证。 这两种方式都可以通过验证,验证完成之后,我们才能完成表单的提交,比如完成登录、注册等操作。 这种验证码叫什么名字? 这个验证码就是 Google 的 ReCAPTCHA V2 验证码,它就属于行为验证码的一种,这些行为包括点选复选框、选择对应图片、语音听写等内容,只有将这些行为校验通过,此验证码才能通过验证。相比于一般的图形验证码来说,此种验证码交互体验更好、安全性会更高、破解难度更大。

其实上文所介绍的验证码仅仅是 ReCAPTCHA 验证码的一种形式,是 V2 的显式版本,另外其 V2 版本还有隐式版本,隐式版本在校验的时候不会再显式地出现验证页面,它是通过 JavaScript 将验证码和提交按钮进行绑定,在提交表单的时候会自动完成校验。除了 V2 版本,Google 又推出了最新的 V3 版本,reCAPTCHA V3 验证码会为根据用户的行为来计算一个分数,这个分数代表了用户可能为机器人的概率,最后通过概率来判断校验是否可以通过。其安全性更高、体验更好。

体验

那哪里可以体验到 ReCAPTCHA 呢?我们可以打开这个网站:https://www.google.com/recaptcha/api2/demo,建议科学上网,同时用匿名窗口打开,这样的话测试不会受到历史 Cookies 的干扰,如图所示:

这时候,我们可以看到下方有个 ReCAPTCHA 的窗口,然后点击之后就出现了一个验证图块。

当然靠人工是能解的,但对于爬虫来说肯定不行啊,那怎么自动化解呢?

接下来我们就来介绍一个简单好用的平台。

解决方案

本次我们介绍的一个 ReCAPTCHA 破解服务叫做 YesCaptcha,主页是 http://yescaptcha.365world.com.cn/,它现在同时可以支持 V2 和 V3版本的破解。

我们这次就用它来尝试解一下刚才的 ReCAPTCHA 上的 V2 类型验证码:https://www.google.com/recaptcha/api2/demo

简单注册之后,可以找到首页有一个 Token。我们可以复制下来以备后面使用,如图所示:

它有两个关键的 API,一个是创建验证码服务任务,另一个是查询任务状态,API 如下:

  • 创建任务:http://api.yescaptcha.365world.com.cn/v3/recaptcha/create

  • 查询状态:http://api.yescaptcha.365world.com.cn/v3/recaptcha/status

API 文档可以参考这里:http://docs.yescaptcha.365world.com.cn/

经过 API 文档可以看到使用的时候可以配置如下参数:

参数名 是否必须 说明
token 请在个人中心获取 (Token)
siteKey ReCaptcha SiteKey (固定参数)
siteReferer ReCaptcha Referer (一般也为固定参数)
captchaType ReCaptchaV2(默认) / ReCaptchaV3
siteAction ReCaptchaV3 选填 Action动作 默认verify
minScore ReCaptchaV3 选填 最小分数(0.1-0.9)

这里就有三个关键信息了:

  • token:就是刚才我们在 YesCaptcha 上复制下来的参数

  • siteKey:这个是 ReCAPACHA 的标志字符串,稍后我们会演示怎么找。

  • siteReferer,一般是 ReCAPTCHA 的来源网站的 Referer,比如对于当前的案例,该值就是 https://www.google.com/recaptcha/api2/demo

那 siteKey 怎么找呢?其实很简单,我们看下当前 ReCAPTCHA 的 HTML 源码,从源码里面找一下就好了:

这里可以看到每个 ReCAPTCHA 都对应一个 div,div 有个属性叫做 date-sitekey,看这里的值就是:

1
6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-

好,万事俱备了,只差代码了!

开工

我们就用最简单 requests 来实现下吧,首先把常量定义一下:

1
2
3
4
TOKEN = '50a07xxxxxxxxxxxxxxxxxxxxxxxxxf78'  # 请替换成自己的TOKEN
REFERER = 'https://www.google.com/recaptcha/api2/demo'
BASE_URL = 'http://api.yescaptcha.365world.com.cn'
SITE_KEY = '6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-' # 请替换成自己的SITE_KEY

这里我们定义了这么几个常量:

  • TOKEN:就是网站上复制来的 token

  • REFERER:就是 Demo 网站的链接

  • API_BASE_URL:就是 YesCaptcha 的 API 网址

  • SITE_KEY:就是刚才我们找到的 data-sitekey

然后我们定义一个创建任务的方法:

1
2
3
4
5
6
7
8
9
10
def create_task():
url = f"{BASE_URL}/v3/recaptcha/create?token={TOKEN}&siteKey={SITE_KEY}&siteReferer={REFERER}"
try:
response = requests.get(url)
if response.status_code == 200:
data = response.json()
print('response data:', data)
return data.get('data', {}).get('taskId')
except requests.RequestException as e:
print('create task failed', e)

这里就是调 API 来创建任务,没什么好说的。

如果创建成功之后会得到一个 task_id,接下来我们就需要用这个 task_id 来轮询查看任务的状态,定义如下的这么一个方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def polling_task(task_id):
url = f"{BASE_URL}/v3/recaptcha/status?token={TOKEN}&taskId={task_id}"
count = 0
while count < 120:
try:
response = requests.get(url)
if response.status_code == 200:
data = response.json()
print('polling result', data)
status = data.get('data', {}).get('status')
print('status of task', status)
if status == 'Success':
return data.get('data', {}).get('response')
except requests.RequestException as e:
print('polling task failed', e)
finally:
count += 1
time.sleep(1)

这里就是设置了最长轮询次数 120 次,请求的 API 就是查询任务状态的 API,会得到一个任务状态的结果,如果结果是 Success,那就证明任务成功了,解析其中的 response 结果就是验证码破解之后得到的 token。

两个方法调用一下:

1
2
3
4
5
if __name__ == '__main__':
task_id = create_task()
print('create task successfully', task_id)
response = polling_task(task_id)
print('get response:', response[0:40]+'...')

运行结果类似如下:

1
2
3
4
5
6
7
8
9
response data: {'status': 0, 'msg': 'ok', 'data': {'taskId': '1479436991'}}
create task successfully 1479436991
polling result {'status': 0, 'msg': 'ok', 'data': {'status': 'Working'}}
status of task Working
polling result {'status': 0, 'msg': 'ok', 'data': {'status': 'Working'}}
status of task Working
polling result {'status': 0, 'msg': 'ok', 'data': {'status': 'Working'}}
status of task Working
polling result {'status': 0, 'msg': 'ok', 'data': {'status': 'Success', 'response': '03AGdBq27-ABqvNmgq96iuprN8Mvzfq6_8noknIed5foLb15oWvWVksq9KesDkDd7dgMMr-UmqULZduXTWr87scJXl3djhl2btPO721eFAYsVzSk7ftr4uHBdJWonnEemr9dNaFB9qx5pnxr3P24AC7cCfKlOH_XARaN4pvbPNxx_UY5G5fzKUPFDOV14nNkCWl61jwwC0fuwetH1q99r4hBQxyI6XICD3PiHyHJMZ_-wolcO1R9C90iGQyjzrSMiNqErezO24ODCiKRyX2cVaMwM9plbxDSuyKUVaDHqccz8UrTNNdJ4m2WxKrD9wZDWaSK10Ti1LgsqOWKjKwqBbuyRS_BkSjG6OJdHqJN4bpk_jAcPMO13wXrnHBaXdK4FNDR9-dUvupHEnr7QZEuNoRxwl8FnO2Fgwzp2sJbGeQkMbSVYWdAalE6fzJ8NwsFJxCdDyeyO817buBtvTJ4C06C1uZ92fpPTeYGJwbbicOuqbGfHNTyiSJeRNmt-5RKz0OUiPJOPnmVKGlWBOqwbwCW1WZt-E-hH4FEg4En5TITmmPb_feS9dWKUxudn1U0hHk2vV9PerjZLtI7F67KtgmcqRrARPbwnc6KyAi3Hy1hthP92lv4MRIcO2jx0Llvsja-G2nhjZB0ZoJwkb9106pmqldiwlXxky4Dcg7VPStiCYJvhQpRYol7Iq1_ltU2tyhMqsu_Xa8Z6Mr5ykRCLnmlLb8DV8isndrdwp84wo_vPARGRj7Up9ov-ycb5lDKTf1XRaHiMCa8d2WLy0Pjco9UnsRAPw0FW3MsBJah6ryHUUDho7ffhUUgV1k86ryJym6xbWch1sVC4D5owzrCFn6L-rSLc5SS1pza2zU5LK4kAZCmbXNRffiFrhUY8nP4T1xaR2KMhIaN8HhJQpR8sQh1Azc-QkDy4rwbYmxUrysYGMrAOnmDx9z7tWQXbJE4IgCVMx5wihSiE-T8nbF5y1aJ0Ru9zqg1nZ3GSqsucSnvJA8HV5t9v0QSG5cBC1x5HIceA-2uEGSjwcmYOMw8D_65Dl-d6yVk1YN2FZCgMWY5ewzB1RAFN1BMqKoITQJ64jq3lKATpkc5i7aTA2bRGQyXrbDyMRIrVXKnYMHegfMbDn0l4O81a8vxmevLspKkacVPiqLsAe-73jAxMvsOqaG7cKxMQO9CY3qbtD55YgN0W4p2jyNSVz3aEpffHRqYyWMsRI5LddLgaZQDoHHgGUhV580PSIdZJ5eKd0gOjxIYxKlr0IgbMWRmsG_TgDNImy1c5oey8ojl-zWpOQW7bnfq5Z4tZ10_sCTfoOZVLqRuOsqB1OOO9pLRQojLBP0HUiGhRAr_As9EIDu6F9NIQfdAmCaVvavJbi1CZITFjcywP-tBrHsxpwkCXlwl996MK_XyEDuyWnJVGiVSthUMY306tIh1Xxj93W3KQJCzsfJQcjN-3lGLLeDFddypHyG4yrpRqRHHBNyiNJHgxSk5SaShEhXvByjkepvhrKX3kJssCU04biqqmkrQ49GqBV9OsWIy0nN3OJTx8v05MP8aU8YYkYBF01UbSff4mTfLAhin6iWk84Y074mRbe2MbgFAdU58KnCrwYVxcAR8voZsFxbxNwZXdVeexNx5HlIlSgaAHLWm2kFWmGPPW-ZA7R8Wst-mc7oIKft5iJl8Ea0YFz8oXyVgQk1rd9nDR3xGe5mWL1co0MiW1yvHg'}}

如果其返回的是如上格式的数据,就代表 ReCAPTCHA 验证码已经识别成功了,其返回的 response 字段的内容就是识别的 token,我们直接拿着这个 token 放到表单里面提交就成功了。

那这个 token 怎么来用呢? 其实如果我们用浏览器验证验证成功之后,点击表单提交的时候,在其表单里面会把一个 name 叫做 g-recaptcha-response 的 textarea 赋值,如果验证成功,它的 value 值就是验证之后得到的 token,这个会作为表单提交的一部分发送到服务器进行验证。如果这个字段校验成功了,那就没问题了。

所以,如上的过程相当于为我们模拟了点选验证码的过程,其最终得到的这个 token 其实就是我们应该赋值给 name 为 g-recaptcha-response 的内容。 那么怎么赋值呢? 很简单,用 JavaScript 就好了。我们可以用 JavaScript 选取到这个 textarea,然后直接赋值即可,代码如下:

1
document.getElementById("g-recaptcha-response").innerHTML="TOKEN_FROM_YESCAPTCHA";

注意这里的 TOKEN_FROM_YESCAPTCHA 需要换成刚才我们所得到的 token 值。我们做爬虫模拟登录的时候,假如是用 Selenium、Puppeteer 等软件,在模拟程序里面,只需要模拟执行这段 JavaScript 代码,就可以成功赋值了。 执行之后,直接提交表单,我们查看下 Network 请求:

可以看到其就是提交了一个表单,其中有一个字段就是 g-recaptcha-response,它会发送到服务端进行校验,校验通过,那就成功了。 所以,如果我们借助于 YesCaptcha 得到了这个 token,然后把它赋值到表单的 textarea 里面,表单就会提交,如果 token 有效,就能成功绕过登录,而不需要我们再去点选验证码了。 最后我们得到如下成功的页面:

当然我们也可以使用 requests 来模拟完成表单提交:

1
2
3
4
5
6
def verify(response):
url = "https://www.google.com/recaptcha/api2/demo"
data = {"g-recaptcha-response": response}
response = requests.post(url, data=data)
if response.status_code == 200:
return response.text

最后完善一下调用:

1
2
3
4
5
6
7
if __name__ == '__main__':
task_id = create_task()
print('create task successfully', task_id)
response = polling_task(task_id)
print('get response:', response[0:40]+'...')
result = verify(response)
print(result)

运行结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
response data: {'status': 0, 'msg': 'ok', 'data': {'taskId': '1479436991'}}
create task successfully 1479436991
polling result {'status': 0, 'msg': 'ok', 'data': {'status': 'Working'}}
status of task Working
polling result {'status': 0, 'msg': 'ok', 'data': {'status': 'Working'}}
status of task Working
polling result {'status': 0, 'msg': 'ok', 'data': {'status': 'Working'}}
status of task Working
polling result {'status': 0, 'msg': 'ok', 'data': {'status': 'Success', 'response': '03AGdBq27-ABqvNmgq96iuprN8Mvzfq6_8noknIed5foLb15oWvWVksq9KesDkDd7dgMMr-UmqULZduXTWr87scJXl3djhl2btPO721eFAYsVzSk7ftr4uHBdJWonnEemr9dNaFB9qx5pnxr3P24AC7cCfKlOH_XARaN4pvbPNxx_UY5G5fzKUPFDOV14nNkCWl61jwwC0fuwetH1q99r4hBQxyI6XICD3PiHyHJMZ_-wolcO1R9C90iGQyjzrSMiNqErezO24ODCiKRyX2cVaMwM9plbxDSuyKUVaDHqccz8UrTNNdJ4m2WxKrD9wZDWaSK10Ti1LgsqOWKjKwqBbuyRS_BkSjG6OJdHqJN4bpk_jAcPMO13wXrnHBaXdK4FNDR9-dUvupHEnr7QZEuNoRxwl8FnO2Fgwzp2sJbGeQkMbSVYWdAalE6fzJ8NwsFJxCdDyeyO817buBtvTJ4C06C1uZ92fpPTeYGJwbbicOuqbGfHNTyiSJeRNmt-5RKz0OUiPJOPnmVKGlWBOqwbwCW1WZt-E-hH4FEg4En5TITmmPb_feS9dWKUxudn1U0hHk2vV9PerjZLtI7F67KtgmcqRrARPbwnc6KyAi3Hy1hthP92lv4MRIcO2jx0Llvsja-G2nhjZB0ZoJwkb9106pmqldiwlXxky4Dcg7VPStiCYJvhQpRYol7Iq1_ltU2tyhMqsu_Xa8Z6Mr5ykRCLnmlLb8DV8isndrdwp84wo_vPARGRj7Up9ov-ycb5lDKTf1XRaHiMCa8d2WLy0Pjco9UnsRAPw0FW3MsBJah6ryHUUDho7ffhUUgV1k86ryJym6xbWch1sVC4D5owzrCFn6L-rSLc5SS1pza2zU5LK4kAZCmbXNRffiFrhUY8nP4T1xaR2KMhIaN8HhJQpR8sQh1Azc-QkDy4rwbYmxUrysYGMrAOnmDx9z7tWQXbJE4IgCVMx5wihSiE-T8nbF5y1aJ0Ru9zqg1nZ3GSqsucSnvJA8HV5t9v0QSG5cBC1x5HIceA-2uEGSjwcmYOMw8D_65Dl-d6yVk1YN2FZCgMWY5ewzB1RAFN1BMqKoITQJ64jq3lKATpkc5i7aTA2bRGQyXrbDyMRIrVXKnYMHegfMbDn0l4O81a8vxmevLspKkacVPiqLsAe-73jAxMvsOqaG7cKxMQO9CY3qbtD55YgN0W4p2jyNSVz3aEpffHRqYyWMsRI5LddLgaZQDoHHgGUhV580PSIdZJ5eKd0gOjxIYxKlr0IgbMWRmsG_TgDNImy1c5oey8ojl-zWpOQW7bnfq5Z4tZ10_sCTfoOZVLqRuOsqB1OOO9pLRQojLBP0HUiGhRAr_As9EIDu6F9NIQfdAmCaVvavJbi1CZITFjcywP-tBrHsxpwkCXlwl996MK_XyEDuyWnJVGiVSthUMY306tIh1Xxj93W3KQJCzsfJQcjN-3lGLLeDFddypHyG4yrpRqRHHBNyiNJHgxSk5SaShEhXvByjkepvhrKX3kJssCU04biqqmkrQ49GqBV9OsWIy0nN3OJTx8v05MP8aU8YYkYBF01UbSff4mTfLAhin6iWk84Y074mRbe2MbgFAdU58KnCrwYVxcAR8voZsFxbxNwZXdVeexNx5HlIlSgaAHLWm2kFWmGPPW-ZA7R8Wst-mc7oIKft5iJl8Ea0YFz8oXyVgQk1rd9nDR3xGe5mWL1co0MiW1yvHg'}}
status of task Success
get response: 03AGdBq27-ABqvNmgq96iuprN8Mvzfq6_8noknIe...
<!DOCTYPE HTML><html dir="ltr"><head><meta http-equiv="content-type" content="text/html; charset=UTF-8"><meta name="viewport" content="width=device-width, user-scalable=yes"><title>ReCAPTCHA demo</title><link rel="stylesheet" href="https://www.gstatic.com/recaptcha/releases/TbD3vPFlUWKZD-9L4ZxB0HJI/demo__ltr.css" type="text/css"></head><body><div class="recaptcha-success">Verification Success... Hooray!</div></body></html>

最后就可以发现,模拟提交之后,结果会有一个 Verification Success... Hooray! 的文字,就代表验证成功了!

至此,我们就成功完成了 ReCAPTCHA 的破解。

上面我们介绍的是 requests 的实现,当然使用 Selenium 等工具也可以实现,具体的 Demo 在文档也写好了,请大家参考文档的说明使用即可。

小福利

现在 YesCaptcha 的价格我觉得相比之前介绍过的 2Captcha 实惠很多了,它破解一次是花费 10 点数,10 块钱是 10000 点数,所以平均破解一次验证码一分钱,新用户是送 1000 点数,可以破解 100 次。我个人觉得很实惠了。

大家有需要的话可以试试看!

溜了溜了~

更多精彩内容,请关注我的公众号「进击的 Coder」和「崔庆才丨静觅」。

技术杂谈

今天逛 GitHub 的时候发现了 GitHub 出了一个新的 Feature,叫做 GitHub Copilot,说可以帮我们自动写代码!

网址是这个:

点进来之后就几个醒目的大字 - Your AI pair programmer,你的人工智能编程伙伴。

这里有几个示例是这样的:

怎么个人工智能法呢?

这里前 7 行都是人写的,后面的 17 行都是人工智能帮写的。

我们需要写啥呢?我们来详细看看这个例子。

首先创建了一个 sentiment.ts 文件,然后引入了一个 Node.js 的包叫做 fetch-h2,然后写了两行注释:

1
2
// Determine whether the sentiment of text is positive
// Use a web service

什么意思呢?就是用注释写了我要写个啥东西,翻译过来如下:

  • 判断一句话的包含的情感是正面的还是负面的。(比如说“我好开心”就包含了积极情绪,句子包含的情感就是正面的;比如“你太坏了”就包含了负面评价,句子的情感就是负面的。)

  • 使用 Web 服务来实现。

然后定义了一个方法的声明:

1
async function isPositive(text: string): Promise<boolean>

没了。

就导入了一个包,然后写了两句注释,定义了一个方法的参数和返回值,人做的事就这么多。

然后 GitHub Copilot 就能帮我们把代码写出来,它写的内容如下:

1
2
3
4
5
6
7
8
9
10
  const response = await fetch(`http://text-processing.com/api/sentiment/`, {
method: "POST",
body: `text=${text}`,
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
});
const json = await response.json();
return json.label === "pos";
}

没错,它智能分析了我们人写的注释和方法的声明,然后就把代码写出来了。

这里就调用了一个 API,然后还自动构造了 POST 请求,获取返回结果,然后比对返回结果的 label 是不是 pos,如果是,那就代表句子包含了积极程序,返回 false 就不是积极情绪。

虽然说规范程度上一般,没有异常处理什么的,但是已经相当了不起了有没有!

  • 它居然能准确理解注释中我们描述的两个需求

  • 它居然知道调用哪个 API 来判断文本的情感信息

  • 它居然还能没有语法错误地把一个 JavaScript 方法写出来

牛逼了!

当然它不止能写 JavaScript,还能写很多其他的语言,我们再来看一个 Python的 例子。

这里我们新建了一个 parse_expenses 的 Python 文件,然后定义了一个 parse_expenses 方法,接收一个参数叫做 expenses_string,然后写明注释如下:

1
2
3
4
5
6
7
8
9
"""
Parse the list of expenses and return the list of triples (date, value, currency).
Ignore lines starting with #.
Parse the date using datetime.
Example expenses_string:
2016-01-02 -34.01 USD
2016-01-03 2.59 DKK
2016-01-03 -2.72 EUR
"""

这里就写了,解析下面三行消费数据,然后返回日期、数值、单位,同时要求忽略掉开头是 # 的行,时间要用 datetime 库来解析出来。

然后 AI 就帮我们写了如下代码:

1
2
3
4
5
6
7
8
9
expenses = []
for line in expenses_string.splitlines():
if line.startswith("#"):
continue
date, value, currency = line.split(" ")
expenses.append((datetime.datetime.strptime(date, "%Y-%m-%d"),
float(value),
currency))
return expenses

看完这个我惊呆了,它全都做到了!

  • 跟它说了忽略开头是 # 的行,它就添加了一个判断

  • 而且它智能分析了下面的三行数据是什么格式的,然后还知道用空格把它分开

  • 分开之后,针对日期,他还知道用 datetime 解析一下,而且还知道是什么格式,年月日中间用的是横线

  • 数值还自动转成了 float 类型

  • 最后组成了一个元组返回了

简直,我简直不相信这是 AI 写的,感觉这个作为面试题,人也不一定一次性完整写得很好,AI 全都做到了!

这。

当然除了 JavaScript、Python,它还会很多语言,比如 Go、Ruby、TypeScript 都会。

这是背后究竟是什么技术呢?

看了看官网的介绍,说是基于 OpenAI 做的,官方原话如下:

1
Trained on billions of lines of public code, GitHub Copilot puts the knowledge you need at your fingertips, saving you time and helping you stay focused.

翻译过来就是:

1
GitHub Copilot 接受了数十亿行公共代码的训练,让您所需的知识触手可及,从而节省您的时间并帮助您保持专注。

反正就是他们训练了一个模型,这个模型接受了数十亿行代码作为训练输入,最后就学会了人怎么写代码了。

这波可以。

然后官方还介绍说:

GitHub Copilot 尤其擅长写 Python、Go、Ruby、JavaScript、TypeScript,并且现在已经发布成了 VS Code 中的一个插件。在我们写 Code 的时候,这个插件就会跟 OpenAI 的模型通信,然后目前看到的内容帮助我们自动写出想要的代码,基本流程如下图所示:

好家伙,那我赶紧来下载看看。

到 VS Code 里面搜索下 Copilot,果然有,已经十万多下载量了。

装上之后,它让我登录 GitHub 授权,登录之后,它弹了一个令人悲伤的信息:

它说我现在还没有权限使用,请访问 https://copilot.github.com 申请假如白名单。

也就是还没完全开放使用,需要申请才能用。

于是乎,我就去申请了下,点下网站的 Sign Up 即可,现在我已经在等待名单中了,等通过了我应该就能用了,如图所示:

大家感兴趣的话也赶紧去申请试试吧!

更多精彩内容,请关注我的公众号「进击的 Coder」和「崔庆才丨静觅」。

技术杂谈

最近在实现一个功能,那就是显示图片的分辨率信息。

由于分辨率无非就是宽乘以高的格式嘛,比如 250x140 这样的。

然后我代码里面就实现成了这样子:

1
return `${dimension?.width}x${dimension?.height}`;

dimension 就是分辨率对象,它有宽高两个信息。

然而,一位大佬给我 Review 代码的时候发现了这个问题,他说你看看其他地方是怎么表示的,需不需要不同地区做 Localization(国际化 i18n 处理)?

于是我就找了下 Chrome 浏览器怎么显示的,随便打开了一张图片:

这不就是这么显示的吗?

我写的没错啊?到底问题出在了哪里?

不解之时又去求助大佬,大佬说:

1
Is Chrome using the letter x or are they using the × character?

我恍然大悟,原来是字符问题!

然后我就追踪了下 Chrome 这页的代码:

由于这个信息是在选项卡显示的,那么一定在 title 节点里面,我把这个字符复制了出来,跟字母 x 对比了下:

果然不是字母 x,而是字符 ×,有趣!

后来我改成了 × 就好了,修改如下:

1
return `${dimension?.width}×${dimension?.height}`;

然后告诉了大佬,大佬欣慰地笑了,说:

1
Nice, no localization necessary =)

妙极了!

哈哈,这里就简单记录下,非常有意思,不然我还一直以为是一个字母 x 呢。

以后大家表示分辨率的时候,更标准的形式应该是用字符 × 而不是字母 x,比如应该是:

1
250×160

而不是:

1
250x160

涨姿势了!

彩蛋:我的微信昵称其实也有类似的字符,比如「崔庆才丨静觅」中间的「丨」是一个汉字(发音为 gun),而不是竖线「|」,哈哈哈。

更多精彩内容,请关注我的公众号「进击的 Coder」和「崔庆才丨静觅」。

技术杂谈

VS Code 想必大家都听说过吧,VS Code 凭借其强大的插件生态简直把 IDE 玩出花来了,现在我身边越来越多的程序员朋友现在都转向使用 VS Code 来写代码了,我也不例外。

但大家知道 VS Code 本身是用什么写的吗?没错,其实是 JavaScript 写的,准确来说是用 TypeScript 写的。

这时候我就想问,既然是用 TypeScript 写的,那它有没有网页版呢?

有人就要问了,网页版的 VS Code 有啥用啊,其实它能解决很多痛点:

  • 如果我能在网页版的 VS Code 里面写代码,这样换了一个 PC 之后我只需要打卡这个网页就能接着写了,多么简单方便。

  • 另外我写了代码,想给别人复现现场看效果,直接甩给他一个网页链接就好了。

  • 别人遇到问题想让我帮调试,那他在里面写完了,然后直接给我链接就好了。

舒服吧!

那就来整一个吧!

于是我就找官网的支持,但是没找到官网有说 VS Code 网页版的任何事情。

但是找到了一个开源项目,叫做 code-server,运行之后就可以在浏览器里面打卡 VS Code 了,GitHub 地址是:https://github.com/cdr/code-server

它的官方介绍是:

Run VS Code on any machine anywhere and access it in the browser.

正式我想要的!它在浏览器里面的运行效果如图所示:

安装

接下来那就安装试试吧,它支持多个平台,只需要运行一条命令就能安装了:

1
curl -fsSL https://code-server.dev/install.sh | sh

这条命令运行之后会自动判断当前的平台,然后运行安装步骤。

安装完了之后会有一个可用的 code-server 命令,运行之后便可以在本地启动 code-server 服务了,然后就可以在浏览器中打开 VS Code 了,就像上图所示。

Docker

但这 code-server 仅仅是在本地运行起来了。

如果我想将其部署到公网供我随时访问呢?或者极端一点,如果我想为其他人也部署一个 code-server 怎么办呢?或者我想为一百个人部署自己专属的 code-server 怎么办呢?

所以,这时候一个很好的方案就是上 Docker + Kubernetes 了。

既然要上 Docker,那就顺便对 code-server 做一些基础化的配置吧,比如预装一些插件,比如设置一些主题,比如设置一些编辑器配置等等。

我本地的 VS Code 现在在用一个我个人觉得比较好看的主题,叫做 Material Ocean,效果是这样的:

这是通过安装一个插件实现的,叫做 Material Theme:

另外还有一些基础的插件,比如 Python的支持、自动提示等等。

另外既然要支持 Python 了,那也可以在 Docker 里面配置一些基础的 Python 库,以免使用的时候再安装。

其他的一些配置比如代码规范、缩进、换行等都可以通过 VS Code 的一些 settings.json 配置来实现。

等等还有一些其他的优化项可以自行发挥啦。

基本上就是这么多了,所以接下来就可以写 Dockerfile 了。

这里我直接基于 ubuntu 18.04 来开始搭建了,编写一个 Dockerfile 如下:

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
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
91
92
93
94
95
96
97
FROM ubuntu:18.04

RUN apt-get update && apt-get install -y \
openssl \
net-tools \
git \
zsh \
locales \
sudo \
dumb-init \
vim \
curl \
wget \
bash-completion \
python3 \
python3-pip \
python3-setuptools \
build-essential \
python3-dev \
libssl-dev \
libffi-dev \
libxml2 \
libxml2-dev \
libxslt1-dev \
zlib1g-dev

RUN chsh -s /bin/bash
ENV SHELL=/bin/bash

RUN ARCH=amd64 && \
curl -sSL "https://github.com/boxboat/fixuid/releases/download/v0.4.1/fixuid-0.4.1-linux-$ARCH.tar.gz" | tar -C /usr/local/bin -xzf - && \
chown root:root /usr/local/bin/fixuid && \
chmod 4755 /usr/local/bin/fixuid && \
mkdir -p /etc/fixuid && \
printf "user: coder\ngroup: coder\n" > /etc/fixuid/config.yml

RUN CODE_SERVER_VERSION=3.10.2 && \
curl -sSOL https://github.com/cdr/code-server/releases/download/v${CODE_SERVER_VERSION}/code-server_${CODE_SERVER_VERSION}_amd64.deb && \
sudo dpkg -i code-server_${CODE_SERVER_VERSION}_amd64.deb

RUN locale-gen en_US.UTF-8

ENV LC_ALL=en_US.UTF-8

RUN adduser --disabled-password --gecos '' coder && \
adduser coder sudo && \
echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers;

RUN chmod g+rw /home && \
mkdir -p /home/coder/workspace && \
mkdir -p /home/coder/.local && \
chown -R coder:coder /home/coder && \
chown -R coder:coder /home/coder/.local && \
chown -R coder:coder /home/coder/workspace;

USER coder

RUN git clone --depth 1 https://github.com/junegunn/fzf.git ~/.fzf && \
~/.fzf/install

ENV PASSWORD=${PASSWORD:-P@ssw0rd}

COPY ./extensions /home/coder/.local/extensions

RUN /usr/bin/code-server --install-extension ms-python.python && \
/usr/bin/code-server --install-extension esbenp.prettier-vscode && \
/usr/bin/code-server --install-extension equinusocio.vsc-material-theme && \
/usr/bin/code-server --install-extension codezombiech.gitignore && \
/usr/bin/code-server --install-extension piotrpalarz.vscode-gitignore-generator && \
/usr/bin/code-server --install-extension aeschli.vscode-css-formatter && \
/usr/bin/code-server --install-extension donjayamanne.githistory && \
/usr/bin/code-server --install-extension ecmel.vscode-html-css && \
/usr/bin/code-server --install-extension pkief.material-icon-theme && \
/usr/bin/code-server --install-extension equinusocio.vsc-material-theme-icons && \
/usr/bin/code-server --install-extension eg2.vscode-npm-script && \
/usr/bin/code-server --install-extension ms-ceintl.vscode-language-pack-zh-hans && \
/usr/bin/code-server --install-extension /home/coder/.local/extensions/tkrkt.linenote-1.2.1.vsix && \
/usr/bin/code-server --install-extension dbaeumer.vscode-eslint

RUN /usr/bin/python3 -m pip install -U pip setuptools

RUN /usr/bin/python3 -m pip install requests httpx scrapy aiohttp pyquery beautifulsoup4 \
selenium pyppeteer pylint flask django tornado numpy pandas scipy autopep8

COPY settings.json /home/coder/.local/share/code-server/User/settings.json

RUN sudo chown coder /home/coder/.local/share/code-server/User/settings.json

COPY entrypoint.sh /home/coder/.local/entrypoint.sh

RUN sudo chmod +x /home/coder/.local/entrypoint.sh

WORKDIR /home/coder/workspace

EXPOSE 8080

ENTRYPOINT ["/bin/sh", "/home/coder/.local/entrypoint.sh"]

这里就直接把 Dockerfile 列出来了,主要分这么几步:

  • 装一些基本的环境依赖库

  • 设置 shell

  • 安装 code-server

  • 设置工作目录

  • 安装 code-server 插件

  • 安装 Python 常用的库

  • 设置 VS Code 的 settings

  • 设置运行目录

  • 设置运行脚本入口

比如 VS Code 插件我就提前装好了 Material Theme 插件,然后在 settings.json 里面启用对应的主题即可:

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
{
"workbench.colorTheme": "Material Theme",
"workbench.iconTheme": "material-icon-theme",
"git.enableSmartCommit": true,
"editor.tabSize": 2,
"editor.detectIndentation": false,
"editor.formatOnSave": true,
"editor.formatOnPaste": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.fontSize": 16,
"editor.suggestSelection": "first",
"files.autoGuessEncoding": true,
"files.autoSave": "afterDelay",
"terminal.integrated.inheritEnv": false,
"vetur.experimental.templateInterpolationService": true,
"[typescript]": {
"editor.tabSize": 2
},
"[javascript]": {
"editor.tabSize": 2
},
"[python]": {
"editor.tabSize": 4,
"editor.defaultFormatter": "ms-python.python"
}
}

这里配置文件主要配置了主题、字体大小、缩进等内容,当然这个如果你要自己配置的话就按照自己的喜好来就好了。

最后通过 docker-compose.json 文件配置镜像信息:

1
2
3
4
5
6
7
8
version: "3"
services:
code-server:
container_name: "code-server"
build: .
image: "germey/code-server"
ports:
- "8080:8080"

OK,基本就是这样,运行:

1
docker-compose build

就可以成功构建一个镜像了,然后运行:

1
docker-compose push

即可把镜像 push 到我的 Docker Hub 上面,等待部署即可。

Kubernetes 部署

对于部署 Kubernetes 来说,如果要做到方便部署且灵活管理的话,那就不得不用到 Helm 了,我可以写一个 Helm Chart,定义好一些模板文件和占位符,同时设置默认的配置选项,这样我们就可以通过一条简单的命令来部署这个 Docker 镜像了。

如果你没有用过 Helm 的话可以搜索相关资料了解下。

这里 Chart 的具体实现我就不再赘述了,主要就包括了几个部分:

  • Deployment

  • Service

  • Ingress

  • PersistentVolumeClaim

  • ServiceAccount

  • Secret

具体的配置我都放在 GitHub 了:https://github.com/Python3WebSpider/CodeServer/tree/master/chart,需要的话自取即可。

有了 Chart 之后,我只需要一条命令即可部署一个在线的 VS Code,命令如下:

1
helm install code-server-<username> . --namespace <namespace> --set user=<username> --set password=<password>

注意运行目录在 chart 路径下才可以。

这里我们传入了 usrname、namespace、password。

这里我配置了解析域名 code-.scrape.center,比如我要配置一个 code-germey.scrape.center,密码是 1234,那就只需要运行该命令即可:

1
helm install code-server-germey . --namespace scrape --set user=germey --set password=1234

这里用户名我替换成了 germey,命名空间我用了 scrape,密码用了 1234。

运行这条命令之后,我就能得到一个 https://code-germey.scrape.center/ 网站了。

没错,就是一条命令部署一个 VS Code,而且有专属域名。

打开之后效果如下:

输入对应的密码之后,就可以进入对应的 VS Code 编辑器页面了,如图所示:

这里我可以新建 Python 文件,然后在线运行:

另外还可以在命令行下像在 Linux 下一样操作,比如安装一个新的 Python 库:

1
pip3 install pillow

非常方便。

另外插件页面也可以看到我安装的一些插件:

也可以在此继续添加想要的插件。

全屏之后活脱脱就是一个桌面版本的 VS Code!

唯一不是很方便的就是在里面跑一些 Web 服务,因为 Web 服务相当于在 Docker 里面运行的,不过不要紧,我们只需要在 Chart 里面增加几个端口映射就好了。

有了它,我在里面写了代码,切换了不同 PC,我不用再关心代码的同步问题了。另外我就可以一键给别人分配一个 Online 版本的 VS Code,别人写了代码之后也可以方便拿给我看问题,也方便直接给别人分享我写的代码和运行效果,简直不要太爽了!

更多精彩内容,请关注我的公众号「进击的 Coder」和「崔庆才丨静觅」。

Python

之前我们了解了一些验证码的处理流程,比如图形验证码、滑块验证码、点选验证码等等,但是这些验证码都有一种共同的特点,那就是这些验证码的处理流程通常只需要在 PC 上完成即可,比如图形验证码如果在 PC 上出现,那么在 PC 上直接验证通过就好了,所有的识别、验证输入的流程都是在 PC 上完成的。

但还有一种验证码和此种情况不同,那就是手机验证码,比如 PC 上需要输入手机号,然后短信验证码需要发到手机上,然后再在 PC 上把收到的验证码输入即可通过验证。

那遇到这种情况,我们如何才能将这个流程给自动化呢?

验证码收发

通常来说,我们的自动化脚本会运行在 PC 上,比如打开一个网页,然后模拟输入手机号,然后点击获取验证码,接下来就需要输入验证码了。打开页面,输入手机号、点击获取验证码等流程我们可以非常容易地实现自动化,但是验证码被发送到手机上了,我们怎么能把它转到 PC 上呢?

为了自动化整个验证码收发的流程,这时候我们想要完成的就是——当手机收到一条短信的时候,它能够自动将短信转发到某处,比如一台远程服务器上或者直接发到 PC 上,在 PC 上我们可以通过一些方法再把短信获取下来并提取验证码的内容,然后自动化填充验证码即可。

那这里关键的部分其实就是怎样完成这两个步骤:

  • 如何监听手机收到了短信

  • 如何将手机短信转发到想要的位置

这两个步骤缺一不可,而且都需要在手机上完成。

解决思路自然很简单了,我们以 Android 手机为例,如果有 Android 开发经验的话,其实这两个功能实现起来还是蛮简单的。

注意:这里我们仅仅简单介绍基本的思路,不会完全详细展开介绍具体的代码实现,感兴趣的话可以自行尝试。

首先如何监听手机收到了短信呢?

在 Android 开发中,整体就分为三个必要环节:

  • 注册读取短信的权限:在一个 Android App 中,读取短信是需要特定的权限的,所以我们需要在 Andriod App 的 AndroidManifest.xml 中将读取短信的权限配置好,比如接收短信的权限配置如下:
1
<uses-permission android:name="android.permission.RECEIVE_SMS"></uses-permission>
  • 注册广播事件:Android 有一个基本组件叫做 BroadcastReceiver,也就是广播接收者的意思,我们可以用它来监听来自系统的各种事件广播,比如系统电量不足的广播、系统来电的广播,当然系统收到短信的广播也就不在话下了。所以这就类似我们注册一个监听器,用来监听系统收到短信的事件。
    比如这里我们可以同样在 AndroidManifest.xml 里面注册一个 BroadcastReceiver,叫做 SmsReciver:
1
2
3
4
5
<receiver android:name=".receive.SmsReciver">
<intent-filter android:priority="999">
<action android:name="android.provider.Telephony.SMS_RECEIVED"/>
</intent-filter>
</receiver>
  • 实现短信广播接收:这里就需要我们真正实现短信接收的逻辑了,这里只需要实现一个 SmsReceiver 类来继承一个 BroadcastReceiver 然后实现其 onReceive 方法即可,其中 intent 参数里面便包含了我们想要的短信息内容,实现如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class SmsReciver extends BroadcastReceiver {

@Override
public void onReceive(Context context, Intent intent) {
Bundle bundle = intent.getExtras();
SmsMessage msg = null;
if (null != bundle) {
Object[] smsObj = (Object[]) bundle.get("pdus");
for (Object object : smsObj) {
msg = SmsMessage.createFromPdu((byte[]) object);
Log.e("短信号码", "" + msg.getOriginatingAddress());
Log.e("短信内容", "" + msg.getDisplayMessageBody());
Log.e("短信时间", "" + msg.getTimestampMillis());
}
}
}

如此一来,我们便实现了短信的接收。

短信收到之后,发送自然也就很简单了,比如服务器提供一个 API,我们通过请求该 API 即可实现数据的发送,这个通过 Android 的一些 HTTP 请求库就可以实现,比如 OkHttp 等构造一个 HTTP 请求即可,这里就不再赘述了。

不过总的来说,整个流程下来其实还需要花费一些开发成本的,对于如此常用的功能,有没有现成的解决方案呢?自然是有的。我们可以借助于于一些开源实现,我们就没必要重复造轮子了。

这里我们就介绍一个开源软件,叫做 SmsForwarder,中文翻译过来叫做短信转发器,其 GitHub 仓库地址为:https://github.com/pppscn/SmsForwarder。

它的基本流程架构图如下:

架构图非常清晰,SmsForwarder 可以监听监听收到短信的事件,获取到短信的来源号码、接受卡槽、短信内容、接收时间等内容,然后将其通过一定的规则转发出去,支持转发到邮箱、微信群机器人、企业微信、Telegram 机器人、Webhook 等。

比如我们可以配置类似这样的规则,如图所示:

转发规则

比如当手机号符合一定的规则就转发到 QQ 邮箱,比如内容包含“报警”就转发到阿里企业邮箱,比如内容开头是“测试”就发动给叫做 TSMS 的 Webhook。

其中QQ邮箱、阿里企业邮箱都是我们已经配置好的发送方,都属于邮箱类型,TSMS 也是一种发送方,属于 Webhook 类型,如图所示:

发送方

我们也可以点击添加发送方按钮来添加对应的发送方,比如添加邮箱的发送方,我们可以设置 SMTP 配置下发件邮箱、SMTP 服务器、SMTP 端口、授权密码等内容:

添加/编辑发送方邮箱

设置 Webhook 我们可以选择是 GET 还是 POST 请求,然后填入对应的 URL、密钥等内容:

添加/编辑发送方网页通知

设置转发规则页面如图所示:

支持正则匹配规则 & 支持卡槽匹配规则

比如这里我们可以选择匹配卡槽、匹配的字段、匹配的模式,还可以配置正则来设置匹配的值,这里就配置了尾号是 4566 的手机号来执行一定的发送操作,收到的短信会发送到钉钉这个发送方。

实战演示

比如这里我们来尝试下,这里我们用 Flask 写一个 API,实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from flask import Flask, request, jsonify
from loguru import logger

app = Flask(__name__)


@app.route('/sms', methods=['POST'])
def receive():
sms_content = request.form.get('content')
logger.debug(f'received {sms_content}')
# parse content and save to db or mq
return jsonify(status='success')


if __name__ == '__main__':
app.run(debug=True)

代码很简单,这里设置了一个路由,接收 POST 请求,然后读取了 Request 表单的内容,其中 content 就是短信的详情内容,然后将其打印出来。

我们将代码保存为 server.py,然后将其运行起来:

1
python3 server.py

运行结果输出如下:

1
2
3
4
5
* Debug mode: on
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
* Restarting with stat
* Debugger is active!
* Debugger PIN: 269-657-055

为了方便测试,我们可以用 Ngrok 将该服务暴露到公网:

1
ngrok http 5000

注意:Ngrok 可以方便地将任何非公网的服务暴露到公网访问,并配置特定的临时二级域名,但一个域名有时长限制,所以通常仅供测试使用。试用前请先安装 Ngrok,具体可以参考 https://ngrok.com/。

运行之后,可以看到输入结果如下:

1
2
3
4
5
6
7
8
9
10
11
Session Status                online                                                                                                   
Session Expires 1 hour, 59 minutes
Update update available (version 2.3.40, Ctrl-U to update)
Version 2.3.35
Region United States (us)
Web Interface http://127.0.0.1:4040
Forwarding http://1259539cb974.ngrok.io -> http://localhost:5000
Forwarding https://1259539cb974.ngrok.io -> http://localhost:5000

Connections ttl opn rt1 rt5 p50 p90
9 0 0.00 0.00 0.00 0.00

这里我们可以看到 Ngrok 为我们配置了一个公网地址,比如访问 https://1259539cb974.ngrok.io 即相当于访问了我们本地的 http://localhost:5000 服务,这样手机上只需要配置这个地址即可将数据发送到 PC 了。

接下来我们手机上打开 SmsForder,添加一个 Webhook 类型的发送方,配置如下:

这里 Server 的地址我们就直接设置了刚才 Ngrok 提供的公网地址了,记得 URL 路径后面加上 sms。

接着我们添加一个转发规则:

这里我们设置了内容匹配规则,比如匹配到内容开头为测试的时候,那就将短信内容转发到 Webhook 这个发送方,即发送到我们刚刚搭建的 Flask 服务器上。

OK,配置完成之后,然后我们给该手机尝试发送一个验证码,内容如下:

1
测试验证码593722,一分钟有效。

这时候就可以发现刚才的 Flask 服务器接收结果是这样的:

1
2
3
4
5
received +8617xxxxxxxx
测试验证码593722,一分钟有效。
SIM2_China Unicom_
2021-03-27 18:47:54
SM-G9860

可以看到刚才验证码的内容就成功由手机发送到 PC 了,接着我们便可以对此消息进行解析和处理,然后存入数据库或者消息队列即可。爬虫一端监听消息队列或者数据库改动即可将其填写并进行一些模拟登录操作了,该步骤就不再赘述了。

批量收发

当然以上只针对于一部手机的情况,如果我们有大量的手机和手机卡,我们可以实现手机的群控处理,比如统一安装短信接收软件,统一配置相同的转发规则,从而实现大量手机号验证码的接收和处理。

比如一个群控系统就是这样的:

卡池

当然还有更专业的解决方案,比如有专业的手机卡池,配合以专业的软件设备实现短信的监听。

比如如下的设备支持插 128 张 SIM 卡,就可以实现同时监听 128 个手机号的验证码,如图所示:

具体的技术这里不再阐述,详细可以自行查询相关的设备供应商。

接码平台

当然如上的方案成本还是比较高的,而且这些方案其实已经不限于简单接收短信验证码了,比如手机群控系统一般都会做手机群控爬虫,而卡池也可以用来做 4G/5G 蜂窝代理,如果仅仅做短信收发是可以的,但未免有些浪费了。

如果我们不想耗费过多成本想实现短信验证码的自动化,还有一种方案就是接码平台,其基本思路是这样的:

  • 平台会维护大量的手机号,并可能开放一些 API 或者提供网页供我们调用来获取手机号和查看短信的内容。

  • 我们调用 API 或者爬取网页获取手机号,然后在对应的站点输入该手机号来获取验证码。

  • 通过调用 API 或者爬取网页获取对应手机号短信的内容,并交由爬虫处理。

具体的操作步骤这里就不再详细阐述了,这里简单列几个接码平台:

  • 番薯云:https://guanfangdiping.com/

  • 云际云短信:https://yunjisms.xyz/

  • 接码号:https://jiemahao.com/

  • KaKa 接码:http://www.kakasms.com/

由于接码平台管控比较严格,所以可能随时不可用,请自行搜集对应的平台进行使用。

更多精彩内容,请关注我的公众号「进击的 Coder」和「崔庆才丨静觅」。

技术杂谈

VS Code 之大,简直是无奇不有啊!

这不,前几天我就发现了一个插件,用这个插件我们甚至可以在 VS Code 里面交友!就像一些交友软件一样,喜欢的右滑、不喜欢的左滑,互相喜欢的就匹配成功,然后就可以聊天!进而???

简直是太骚了啊!

究竟是何方神圣呢?

这个 VS Code 插件叫做 vsinder,什么意思?就是 VS Code + Tinder (一个国外交友软件)的简写,Logo 是这样的:

这爱心不能太明显了。

抱着好奇心,我在 VS Code 里面搜了一下,还真有,而且现在已经 3w+ 次安装了:

那就索性来试试吧,点击 install,完了之后在 VS Code 的左侧就会出现一个爱心💗的入口,如图所示:

点击一下,一上来就要求 Sign-in,这里是用的 GitHub 来登录的,点击之后会跳转到 vsinder 的 SSO 登录页面,GitHub 登录成功之后就会提示授权成功:

回到 VS Code,这里就显示 GitHub 的个人信息了,它获取了我的 GitHub 头像、昵称、年龄等信息:

当然我们也可以点击 edit profile 来修改自己的个人信息,点击之后如图所示:

这里可以填写昵称、个人简介、擅长的编程语言、生日等信息,生日是用来计算年龄的。

然后下面还有一个关键的信息,就是你要在这里找什么人?一个是 love 一个是 friendship,如果选了 love,还会提示选择男的还是女的,还有年龄等等:

比如这里我就果断选了 —— friendship!(逃

点击保存之后,还有另外一个入口就是 edit code pics,就是把自己觉得最牛逼的代码贴上,这样才会更多的人右滑对不对!?

我是写 Python 的,那我选什么好呢?

对了,那就选 Python 之禅了!大道至简,浑然天成!

Python 命令行输入:

1
import this

运行结果如下:

这里我就复制一下,粘贴进去了,最多 600 字符,那这里我就直接截断了。

虽然这不是完完全全的 Python 代码,但把 Python 最核心的设计思想说出来了,是不是 Python 里面最牛逼的?(是!

我相信别人看到之后一定会疯狂 like 我的。

接下来保存下,然后我就点击 start swiping 开始滑动了!如图所示:

一上来就滑到一个:

这个人名字叫 Leon,23 岁,写了个代码是用来 walkDir 的,也就是遍历文件目录的,看简介似乎还是个俄语?俄罗斯人啊。

但是,这个一看就不太行,他不用 Python,如果用 Python 的话,直接用 glob 包不就解决的事吗?不行不行,果断点击 x。

这里还有快捷键,如果不喜欢可以点击键盘的左箭头,如果喜欢可以点击右箭头。

这里果断左箭头了!

又来了一个,这人写 Go 的:

但是似乎就会个 Hello World,不行不行,果断还是左箭头了。

接下来一个 19 岁的孩子写了段 Java:

还可以,写了一个 random 选择,随机抽取一句话返回,还行吧,那就 like 一下吧。

然而这时候,我发现了什么?一个聊天按钮?还有个 1?

我跟这个 19 岁的孩子匹配上了??

点击头像还能聊天?

我发了一个:Your code is really cool! (其实也就是那样,小孩子嘛,稍微夸一夸啦

过了一会,也没理我。

估计可能时区不一样,还在睡觉呢吧?

算了我也不给他机会了,点击 ummatch 可以直接取消匹配,这样他就没法给我发消息了:

不一会我又匹配了一个写 Python的,但这会我忘记了他写的代码是什么样子的了怎么办呢?

不用担心,只需要点击他的昵称就又能看到他写的代码了:

他写的是一个 numpy 的调用,看起来还不错的样子。

于是,后面的事情,我就不说了,我们当然是欢乐地聊起了 Python。(手动滑稽

有人说?这个真能找到对象吗?还真能!

这个插件的作者 Ben Awad 做完这个插件之后,录了个视频上传了 Youtube,现场演示了自己是怎么在这里面找对象的。

不仅仅是匹配到了妹子,而且更牛逼的是他仅仅通过几句聊天就轻松拿到了电话号码???

大师!

不愧是大师啊!

相信你也能行的,来试试吧~

更多精彩内容,请关注我的公众号「进击的 Coder」和「崔庆才丨静觅」。

个人随笔

今天早上很早就醒了,激动得睡不着觉,一直期待着篮网和雄鹿的抢七大战。

总体上看,其实雄鹿整体纸面实力还是占优一些,但是篮网有我杜,我始终相信有我杜什么奇迹都会发生。再加上篮网主场,还有一些历史数据,比如雄鹿客场抢七没赢过,还有裁判吹罚对主场有利,还有腾讯女主播不是美娜…反正种种的一切似乎还是偏向篮网的。

比赛开始,第一节我其实就求篮网不挖坑就行了,因为之前几场都是开局先挖一个大坑,然后后面就很难填了,不过好在第一节僵持住了。

第二节和第三节就不说了,反正挺焦灼的,我和我的好朋友,也是阿杜球迷,一直线上交流着,进球我们就庆祝,不进就互相安慰下。

一直到第四节吧,还剩三四分钟那会还是领先的,但是后来一两分钟居然被反超了,最后不到一分钟,落后四分,那会我俩都觉得要凉凉了。

但是阿杜站出来了,最后雄鹿罚球完就剩两分分差。

那会是 109:107,篮网落后 2 分,绝杀 or 加时 or 回家,生死就在一瞬间!!!

那会我真的是感觉心脏都要跳出来了,疯狂刷着文字直播和实时比分,因为文字直播和实时比分比视频快一些。

我简直那几秒激动得快不行了,真的很难想象阿杜在场上是什么心情。

但!我那会刷到了文字直播,突然比分变成了 109:110,我知道进三分了!!!

我那会真的太激动了,绝杀了!!!真的很难以想象那会我是多么欣喜!!!

然后马上视频直播就来了,看着阿杜最后在最后几秒,运球,转身,三分!进了!!!

我那会看到就直接超大声喊出来了!!!太激动了!!!

太牛逼了,阿杜就是永远的神!

然而,看了看回放,阿杜踩线了,是 2 分,最后变成了 109 平,最后进了加时。

也可以吧,还有机会的。

加时一上来篮网就领先了 2 分,后来好几分钟双方都没得分,最后就剩一分钟多一点了吧,篮网还领先 2 分,我觉得是不是稳了。

后来雄鹿字母哥打进追平。

我记得就在这时候,下一个回合让我特别失望,阿杜面临严防没进,但是有前场板,给了空位的哈里斯。我这时候心想,体现你价值的时候到了,哈里斯,你得进啊!!!然而,伴随着打铁声,还是没进…

哈里斯你就不能争口气吗???

113:111 了,篮网最后时刻又落后了 2 分,我的心一下子又揪了起来。

最后几秒钟,最后的时刻,篮网落后两分,哈登把球给阿杜,霍勒迪防得挺好的,但阿杜最后没有再完成绝杀了。球挺正的,但是短了,是真的累了。

我记得阿杜投出的那一刻,眼中充满了期待,但可惜没进,他的眼神又转为了失落和无力,太可惜了。

就这么结束了吗?这么关键的时刻就以这种方式告终了吗?

我就心想,最后还有暂停的吧,教练你都不叫个暂停跑个战术的吗?就算你没啥战术,最后叫个暂停让阿杜休息休息也行啊,搞不懂纳什什么蜜汁操作。

但也没办法吧,球员毕竟也是可以叫暂停的,阿杜应该就是想自己干,最后一个球没有进就是没有进了。

输了,认了。

关于阿杜,毋庸置疑,联盟第一人无需辩驳。阿杜打满全场 53 分钟,48 分,全场一秒钟都没有歇,可谓是倾尽所有,无数次挽救球队于危难之中,阿杜真的是尽力了。尤其第四节最后的绝平,真的太牛逼了!yyds!

关于哈登,哈登也是尽力了吧,我对哈登没有什么怨言,他也是有伤在身,打满全场,虽然说表现不佳,因为没有恢复最佳状态,全场三分也不准也没有多少突破杀伤,但哈登G5 坚持带伤复出,已经连续带伤打了三场了,还是致敬。

关于纳什,我不想多说了,太多太多让我吐槽的了。就说今天最后时刻,即使球员没叫暂停,你叫个暂停布置下战术不行吗?叫个暂停让阿杜休息下不行吗?另外不怪你季后赛全场死用主力,因为毕竟球员也想上场拼尽全力,但是关键时刻,我真的没看出来纳什你有啥战术啊?另外季后赛死用主力没啥,常规赛你还让阿杜每场打那么久,甚至都能上好多次 40 分钟,至于吗?还有,格林今天就打了几分钟,哈里斯这么拉垮你换格林试试不行吗,天王山不记得是谁打出来的了吗?还有我一直很迷惑的,阿利泽约翰逊,常规赛也是拿 20 + 20 的,就这么被你彻底雪藏了?

关于哈里斯,算了,没得说了,关键时刻就是挺不起来的,萎了一个系列赛了,交易走吧。

关于格里芬,很硬,非常硬,防字母防得很好了,进攻也很不错,打出远超薪资的身价,我估计下赛季肯定不会这么低工资了,蔡老板高薪也得拿下啊。

关于布朗,我觉得还挺不错的,很有活力,尤其本场比赛的几个前场板,非常给力。

关于小乔丹,哦,这么久不见我已经不认识你了。

关于字母哥,那一垫脚欧文一直让我耿耿于怀,小脚一挪?阿德托昆博?就冲你这一脚,太脏了!我不会祝福你的,最后总冠军只要不是雄鹿,都好。希望 76 人晋级,让大帝好好教训下字母哥。

最后再说回阿杜吧,我支持阿杜十多年了,每次有阿杜的球我也是密切关注,赢了开心一整天,输了可能难过一整天,今天就这么遗憾输了,挺难过的,挺惋惜的,最后功亏一篑。但不管怎样,阿杜整个赛季带给我的惊喜已经远超我预期了,赛季开始前我很担心阿杜跟腱大伤之后状态大不如前,但到如今,一次次的超神表现带给了我太多的惊喜,身体条件看着也很不错,似乎跟腱伤势的隐患已经非常小了。最后这个系列赛,天王山之战、这场比赛,真的你已经完全倾尽全力,不能要求你更多了,真的,太强了。你用自己的实力让各路的黑子闭上了嘴,用自己一次次投篮证明了自己就是妥妥的联盟第一人。虽然输了,但是你这个赛季带给大家的惊喜,你收获的尊重和认可,我想也是你非常想得到的东西,也为此为你感到高兴。篮网输了,但你没输。

哦对了,再补一句,篮球的比赛,还是得看直播而不是回放,因为只有直播,一切结果是未知的,才能有无限的期待,才能有无限的代入感,才能在这个过程中感受进球霎那间的极致情绪的绽放!就像本场第四节阿杜的绝平球,进球一瞬间肾上腺素飙升到极致的感觉,这或许是篮球带给我的澎湃激情!如果是录播,一切都知道的结果,那是不会有这种感觉的。另外也很感谢和我一起看球的朋友,一同分享喜悦,一起分享感动,真的难能可贵。

阿杜的赛季结束了,我这个赛季也不看球了,腾讯体育也卸载了。

下赛季卷土重来,哥三人健健康康地,继续向前进。

还是那五个字——篮网总冠军!

更多精彩内容,请关注我的公众号「进击的 Coder」和「崔庆才丨静觅」。

个人随笔

运营公众号已经两年多时间了,之前时间还好,原创不少,大家也都挺愿意看的。但最近我观察发现,我的公众号「进击的 Coder」呈现出几个不健康的状态:

  • 总体阅读下滑

  • 粉丝粘性下滑

  • 粉丝数量下滑

其实根本原因还是原创变少了,当然还有其他的影响因素一起导致了这样的情况。

原创是公众号的至关重要的一部分,是公众号的血液,没有原创内容的文章是很难脱颖而出的,粉丝关注关注的其实就是能从中获取一些独到的价值。

但由于最近一段时间原创文章确实少,都在转载一些文章,和其他技术公众号大同小异,都是转来转去的,久而久之,读者就觉得没什么意思,取关什么的那就是自然而然的了。

那为什么原创变少了呢?总结下来有这么几点原因:

  • 我工作比较忙,现在处于项目的关键阶段,项目周期比较赶,确实写原创文章的时间变少了。

  • 由于最近半年和一年写的文章都是爬虫书的一部分,所以不好提前发出来,所以这就导致了公众号的原创文章变得很少。

  • 最近我也开了一个新的公众号「崔庆才丨静觅」,当时就是想在这个小号上简单记录自己的一些想法的,所以在这个小号上写了一些原创,但由于我当时太基于追求小号的质量和增长了,所以不论是技术文章还是个人感悟都发到小号上,「进击的 Coder」就直接转载这个小号上的文章,所以一些原创被分流了。

当然除了原创变少,还有几个导致目前不健康状态的原因:

  • 由于最近我大多为转载的文章,所以读者的一些留言询问一些问题我可能不了解细节,那就可能回答比较浅或者干脆放出来不予回答,跟读者的互动越来越少。

  • 公众号生态的变化,公众号生态早已一片红海,越来越多的创作者都加入公众号,都在竞争流量,转载和广告大量出现,导致读者现在对公众号的打开率越来越低,不论是单个还是总体。

  • 微信公号本身的推荐算法应该也有一定的作用,比如原创文章的权重相比之前我感觉更高了,反而转载过多的文章权重比例进一步下滑。

嗯,基本上就是这样的现状,导致了公号出现了不健康的状态。

经过一些思考和跟小伙伴的讨论,我们得到了一些思考,决定后面转变一下运营思路,大致总结下:

  • 依然坚持原创是根本,唯有原创才是公众号经久不衰的秘诀,所以以后会继续坚持原创,当然由于本人时间原因,每周每个号 1-2 篇应该算是一个小目标,也就是两个号「进击的Coder」和「崔庆才丨静觅」两个号每周会发 2-4 篇原创文章。

  • 关于原创我们也在联系一些渠道,比如如果大家有不错的原创文章,也欢迎前来本公众号投稿。

  • 之前为了每天都发文章,可能着急忙慌地找几篇就转了,质量并不高,最后导致阅读也不好,给读者提供的价值也不够,所以调整了一下策略——非必要不发文。也就是说,如果今天确实没有什么好发的,那就不要制造信息垃圾了。要发就发一些有价值的,高质量的,所以会更加严格控制公号的文章质量。

  • 区分好「进击的 Coder」和「崔庆才丨静觅」两个号的定位,之前我对两个公号的定位并不清晰,甚至有一段时间不论技术文章还是个人感悟都发到「崔庆才丨静觅」上面,「进击的 Coder」一直是转载文章,这样会导致「进击的 Coder」出现上述我说的不健康的状态。所以后面,「进击的 Coder」会侧重于发技术文章,也就是说我平时写的有关技术的所有文章都会放到这个号上面来,包括网络爬虫、技术总结、工具推荐、Web、AI、小知识点等等。「崔庆才丨静觅」更加私人一点,就会发我日常的一些感悟,比如我的工作感想、我的生活状态、我的碎碎念、我的读书笔记等等。如果大家感兴趣的话可以关注下。

  • 增加和读者的互动,之前的一些留言,跟读者的互动性不好,后面会注意多增加一些互动和交流,增加读者粘性。

  • 不定期搞一点小互动,比如送一些小礼品、书等等。

PS:当然毫不避讳地说,由于我也需要公号来获取一些收入,所以有时候公号也会接一些广告的,但是我也会控制好广告的频率和质量,广告也都是一些技术课程类的,大家如果感兴趣希望如果也可以支持下,感激不尽!

就是这样啦,如果大家还想到什么好的点子也欢迎告诉我哈。我还是非常希望我能够通过两个公众号为大家提供更好的内容和价值的。

Fighting!

更多精彩内容,请关注我的公众号「进击的 Coder」和「崔庆才丨静觅」。

个人记录

这件事是前几天发生的,端午假期我出去参加同学婚礼,假期最后一天我坐高铁回北京。

高铁上,一个小男孩子在说话比较大声,而且看起来比较调皮,敲了小桌板几下,发出了一些声响。然后他爸爸就很生气,应该是呵斥了他几句,然后那个孩子就哭了起来。

其实哭得还挺大声的,一直哭了好几分钟,如果一般情况下,听到孩子哭其实是很让人心烦的,但是这次发生的事情让我唯一的感觉却是替孩子感到惋惜和同情。

那个孩子中间说了好几次:“爸爸,是我做的不对吗?”

爸爸一开始没搭理,孩子问了好几次之后,他爸爸就回答:“别说话,你看别人都不说话,你也不要说话啊。”

孩子应该是听到并不是自己想要的答案,然后就接着问:“爸爸,是我做的让你感到失望了吗?”

爸爸还是那句话:“别说话,安静一点。”

然后孩子还在继续问:“爸爸,我不想让你生气和失望,爸爸是我那里做的不对呢?”

爸爸依然还在说:“别说话,你看别人都不说话,你也不要说话。”

卧槽,听到这里我真的感觉这爸爸怎么这样啊!

后面反复了几句,爸爸依然就是那个回复,后来孩子也不再问了,也就不说话了,火车上又安静了起来。

但我内心还是久久不能平静,直到现在想起来真的想说孩子爸爸几句,真的我感觉这件事让我真的很同情这个孩子,而且对爸爸这种态度感到非常的愤怒,怎么能这么培养和教育孩子呢?

孩子在成长的过程中,尤其在这个阶段,他对整个世界没有很好的认知,不知道自己做的事情是不是对的,而且对世界充满了好奇,所以他遇到自己不明白的事情的时候,非常迫切需要得到一个答案和反馈,如果这时候家长可以给予好的引导,对孩子的成长简直太重要了。

而且更让我感到惋惜的是,这个孩子真的算很懂事了,他已经可以说出“爸爸,是我做的让你感到失望了吗?我不想让你生气和失望。”这样的话了,孩子都能表达自己的感受和想法,这真的已经非常好了。

但是他爸爸的回答简直太让人失望和生气了,他非但没有正面回答孩子的所有问题,而且一直在命令孩子不要讲话,完全没有一点正向的反馈,跟孩子好好说说不好吗?

比如就说,宝贝我没有生气和失望,就是刚才的你声音比较大,会影响到其他的乘客,我们要做一个好的孩子,在火车上不要大声讲话以免影响他人,以后我们火车上安静一些,这样就更好了。首先能够正面回答孩子的疑问,然后通过一些正向的激励来帮助孩子了解一些知识和让他认识到应该做的事情,这样不好吗?这孩子听了之后,一方面就知道自己做的是不是对的,应该怎么做才能更好,因为这个孩子本身已经很懂事了,他也想成为一个好的孩子,知道了哪些是对的,我相信这个孩子后面会注意的。而且经过这些事,孩子也能明白一些道理,这是从生活中学到的,对孩子特别重要。

虽然我没有孩子啊,但站一个旁观者的角度,真的觉得家长应该提供一个更好的引导和教育,这样孩子才能更好地成长。

更多精彩内容,请关注我的公众号「进击的 Coder」和「崔庆才丨静觅」。

个人记录

今天比较忙,等到写这篇文章的时候已经第二天了,但是还是不得不写写,因为杜兰特这场天王山之战的表现是在是太牛逼了!

我平时是看 NBA 的,是杜兰特的死忠粉,喜欢杜兰特有十年了,从高中的时候经常听到同学议论 NBA,说雷霆队有个叫杜兰特的很厉害,跟着看了几场球,后来就成了杜兰特的球迷。

我也习惯称杜兰特为阿杜、KD,所以后面文章我就叫阿杜了,感觉比较亲切。

当然现在我也是毫无疑问的的篮网球迷,因为阿杜去哪个球队我就支持哪个球队。从雷霆到勇士到篮网,他也经历过低谷、经历过高光,勇士的两连冠真的发自内心为他感到高兴,但前年他遭遇了跟腱断裂大伤,我也一直在关注各种消息期盼他能王者归来。

这个赛季,终于盼来了阿杜的复出,我特别希望他能回到当时巅峰的状态,希望跟腱的伤势对他的影响尽量是最小的,但也免不了的每场比赛都会担心他的跟腱伤势会不会复发。尤其是到了现在的季后赛,对抗强度上升,更是每场都在祈祷阿杜不要受伤。

幸运的是,这个赛季没有再看到阿杜的跟腱伤势有任何复发迹象,当然他也在尽力保护自己,比如落地的时候会用摔倒的方式缓冲一下力量。每多看一场,我对阿杜跟腱的恢复情况就会越乐观一些,应该算是彻底好了吧,谢天谢地。

扯远了,今天我来就是专门来说说今天阿杜的超神表现的。

可以说,今天的表现,任何华丽的词藻都不足以表达阿杜的牛逼了,简直就是天神下凡!打满全场 48 分钟,一分钟都没有休息,23 投 16 中,砍下 49 分 + 17 篮板 + 10 助攻的超级三双,外加 3 抢断 2 盖帽,率领篮网在一度落后 17 分的情况下、在两大巨星队友都遭遇伤病的情况下、在众多队友都失准的情况下,带领球队完成惊天大翻盘赢下雄鹿,最后一节真的是把我都看哭了,太悲壮了!

关注这场比赛的朋友都知道这场比赛对阿杜来说意味着什么,本来篮网的形势一片大好,但是和雄鹿的系列赛第一场哈登就因为腿筋伤势退场,第四场欧文又被字母歌垫脚无法出战。虽然说哈登今天出战了,但是状态远不及正常水准。上半场一开场就大比分落后,这时候靠的就只有阿杜了。最后的结果大家也都知道了,阿杜天神下凡般的表现把这场比赛硬生生拿下了!把字母哥彻底打服了,把众多对阿杜一直持怀疑态度的群众打服了,把国内第一杜黑徐静雨打服了。我愿称这场比赛就是阿杜彻底的封神之战,是迄今为止阿杜职业生涯最牛逼的一场比赛,没有之一。

先说说徐静雨吧,他是网上一个比较知名的说球的。客观来讲,他确实对一些比赛或者球员的分析有自己的独到见解,这方面我是认可他的。他是库里球迷,一说到库里那就可劲吹,但经过观察,我发现他是阿杜的黑粉,可能是因为阿杜抢了库里两个 FMVP 的原因吧,一直耿耿于怀,然后一个劲说 MVP 比 FMVP 含金量大多了,反正就是库里有的就可劲吹,没有的就可劲贬。一听到他说阿杜不好的我就来气,甚至连 CJ 单换阿杜的话都说出来,就觉得他真是一个无脑黑子,中间我还把他拉黑了一段时间。但最近发现,徐静雨的口风发生了变化,一方面的原因可能是库里没有进季后赛,另一方面却是也是阿杜打出的表现的确让人心服口服,身体条件也观察到愈发硬朗,攻防两端的表现的确是非常超群。今天我专门去看了徐静雨怎么说,一进去就看到几个大字「全力杜已超全力詹」,好家伙,这是彻底被阿杜打服了,视频中他也对阿杜各种赞誉,被阿杜的表现深深折服,甚至都已经说全力杜已经超过 12 年全力詹这样的话了,全网第一杜黑被打服了。

然后说说我心里的感受吧,其实徐静雨说的一番话我也是认同的,就是阿杜需要一个契机来证明自己的超群价值。这时候有人可能就说了,阿杜的能力还需要证明吗?其实在我看来,需要的。

  • 首先在雷霆时期,阿杜的表现足够牛逼了,尤其 13-14 赛季已经证明了自己单核带队的能力,并勇夺得常规赛 MVP,但这毕竟是常规赛。

  • 在雷霆最后一个赛季,阿杜在跟勇士的鏖战中最后被翻盘,最后阿杜就加入了勇士,这也成了阿杜一生中的一个巨大的黑点,网上一众黑子各种骂。当然我不认为这是黑点哈,我觉得阿杜去到更好的地方,先实现自己的冠军梦就好,他做的决定我是完全支持的。

  • 阿杜在勇士夺得了两个冠军,但是在很多人看来,这两个冠军似乎都太简单了,毕竟勇士这个球队本身就太强大了,银河战舰级别的配置,所以外界都觉得这两个冠军没有什么含金量,也在质疑阿杜要是单核带队是不行的。

  • 阿杜的内心是很敏感的,他也很在意外界对自己的看法,想要赢得外界的尊重。所以他离开勇士的一个原因就有想再证明自己的价值。

  • 篮网今年阵容经过一番操作机会变得非常豪华了,阿杜、哈登、欧文,篮网整个阵容可以说都能比得上 17-19 赛季的勇士了,阿杜伤停一段时间,哈登又带队打出逆天表现,于是又有人开始质疑阿杜的带队能力,说阿杜单核带队不行,只能作为一个强力终结点,不能很好地带动队友。

综合以上几点,加上现在特殊的情况,三巨头除了阿杜其他两个都伤了,而且篮网刚刚经历了从 2-0 到 2-2 点局面,被雄鹿连扳两盘,而且上一场欧文刚刚伤了,不论是阵容上还是士气上,篮网都处于很大的劣势。而天王山之战的重要性不言而喻,此时篮网能依靠的就只有阿杜了。

所以如果这一场,阿杜能够爆发并带领球队获胜,就是对自己非常非常好的证明,这是天时造就的机会,证明自己的时候到了!

不负众望,阿杜天神下凡般的表现大家也看到了,不管是进攻终结还是防守盖帽,攻防两端打出来了统治级表现。另外阿杜不仅仅作为一个终结点,还送出了 10 次助攻,不光得分还能串联球队,就是以往的阿杜和哈登的超强结合体,攻防、串联,所有的所有都做到淋漓尽致。另外阿杜对胜利的渴望也是到了极致,打满了全场 48 分钟,一个个无解中投,一次次突破上篮,还有最后的压哨三分,死神镰刀挥舞起来,彻彻底底的统治级表现,这就是一场封神之战,打破所有的质疑,赢得了足够的尊重。所以这也是我愿意把他称之为阿杜生涯最牛逼的比赛,没有之一。

最后,希望篮网全员能够恢复健康,继续向前征战。

篮网总冠军!阿杜 FMVP!

更多精彩内容,请关注我的公众号「进击的 Coder」和「崔庆才丨静觅」。

技术杂谈

大家知道我平时会写一些文章,有的朋友也会经常问我:你是用的什么软件写作的?用什么软件管理这些文章的?

我之前其实有介绍过我正在使用的支持跨平台写作的解决方案,是基于苹果的 iCloud Drive 来实现的。

首先我是非常喜欢用 Markdown 来写内容的。在电脑上,我可以用文本编辑器 Typora,工作空间都是在 iCloud Drive 上,写完之后就会自动同步,然后我在手机上是用了另外一款软件叫做 Blockquote,也能直接打开 iCloud Drive 的文件,手机上也可以编辑 Markdown 文件。

手机上的软件类似这样子:

但是呢,说实话手机上的这款软件我觉得并没有那么好用,但主要因为他能直接打开原生的md文件,而且没有那么花里胡哨的功能,所以当时就选它了。

但这几天由于我出门了,不方便带电脑,所以有时候就想用手机来写,平时我大多数都是用电脑比较多,这次改成手机直接用 Blockquote 还觉得有些不习惯,逐渐感到有些乏力。

于是就想着看看有没有更好用的替代品。

基本需求

我先说一下我的基本的需求:

  • 第一点呢就是我必须要做到跨平台的同步,因为我有好多台设备,在不同的时间在不同的设备上工作,比如上班时间在公司的 iMac 上,下班了在家里的 iMac 上,周末又会带着 Macbook Pro 出去,路上又在用 iPhone,偶尔还会用用 iPad。在这个过程中我想要随时随地把一些东西记录下来,所以我想要实现无缝的协作切换和高效率的实时同步。所以,我的所有的文本和笔记都必须要是跨平台协同同步的,这个是一个最基本的要求。由于我使用的是苹果全家桶,所以说我之前基于 iCloud Drive 就可以很轻松的实现这所有的功能,然后不同平台上用不同的编辑器来编辑就行了。

  • 第二点我想要保留原生态,即文本的平台无关性。我想编辑的内容是实实在在存在的一个 md 后缀的纯文本文件,好多 md 文件就放在一个文件夹下,也有对应的放图片的文件夹,以便于我随时随地能够把一个 md 文件分享出去或者切到另外的软件打开,甚至加到 Git 中保存起来。比如说我之前调研过「熊掌记」,它对 Markdown 支持挺好的,但是当我想获取原始的 Markdown 文件树的时候,发现「熊掌记」已经把它做到平台里面了,我是很难获取整个的 md 文件夹的,所以我就没法用 Git 来管理我所有的 md 文件了。

基于这两点,之前的方案是能实现的,但是手机上的 Markdown 编辑软件我实在觉得乏力。

所以我也在反思,为什么我一定要执着于原生的 md 文件的编辑呢?我思考了一下,我真正想要的就是第一点——跨平台的同步,是不是我在编辑一个真实的 md 文件不重要,如果我用的平台能够支持导入 md,支持导出 md,那不就好了吗?甚至版本控制我也不用做了,如果一个笔记软件能支持版本控制那我直接用不就好了吗?想到这里,觉得之前似乎走到坑里去了,现在其实很多的厂商都已经提供了这样的解决方案,比如说印象笔记、有道云笔记、还有 Notion 等等。各种各样的笔记软件,其实都已经做到全平台的支持和跨平台的同步了。

Notion

经过一番勘查,我找到了 Notion。经过这么几年的发展,Notion 的功能也变得越来越强大了。而且它对 Markdown 也有一定的支持,虽然说它的语法并不是完完全全的 Markdown,但一些特色功能的确很能打动人心。

首先 Notion 的设计思路就挺好的 —— 万物皆是 Block,即一个块。比如一段文本、一张图片、一个视频、一段代码,只不过 Block 有自己的类型,每个 Block 就像一个个小组件,最终组合成了一篇文章。有了 Block 的设计,一些实现上的灵活性就更高了。要添加新的功能只需要加新的 Block 就好了。

另外 Notion 的功能还是很强的,因为 Block 的加持,所以文档不仅仅可以是文档,又可以是一个项目管理图、一个课程表、一个思维导图、一个计划表等等,All in One!

但不能光说好啊,对于不好的地方,我也进行了一番查阅,Notion 有什么不好的呢?其中很多人反馈的就是它的服务器在国外,所以说一些加载延迟比较高,网络同步效率一般。另外一个反馈就是 Notion 对于中文的支持不好,现在我看到 Notion 只支持了韩文还有英文,然后据小道消息,中文的支持。还在计划和测试中,但是不确定什么时候正式上线。还有就是 Notion 因为是一款国际化软件,所以对国内生态支持不好,比如微信登录、国内手机号登录等等。

反正这些问题还是蛮受诟病的。

遇见 wolai

然后找着找着,我就又发现了另外一款软件。叫做我来 wolai,就基本上是一个国内版的 Notion。

wolai 这名字的确有点奇怪啊,但这影响不大,重要的还是功能。

我一开始对这个软件其实还是持有怀疑态度的,因为我总觉得国内的一些公司可能作为刚初创的公司,它的这个质量的把控可能并不是很好,或者是说服务不稳定。另外可能用着用着这个公司就跑路了,倒闭了,这也是有可能的。

但怎么说呢?我个人试用了一下这一款软件,整个我觉得还是挺符合我的需求的。虽然说它是对标了国外的 Notion 所有的一些功能键。和 Notion 非常的相似。但我觉得只要做到好这个软件好用。能够满足我的需求。那他不妨成为我的一个候选。

我还专门去查了一下这个公司的创始人以及他们做事情的一些态度理念。

看了之后,发现 wolai 其实才上线了一年的时间,但功能已经相对完善了,我觉得现在的功能点做的还算不错了。

wolai 的创始人叫马锐拉,知乎主页是:https://www.zhihu.com/people/marila

他们做 wolai 的初衷是这样说的:

最初开始做 wolai ,出于一个非常简单的原因——作为一个管理者,面对当时公司中几十个互相割裂的内部系统、平台,我往往需要通过拼凑信息碎片并手动构建关联,才能完成信息的追溯与重组。循环往复、极为低效地搜寻信息需要耗费巨大的心力,更别说一群人通过这样的方式来进行协作,可想而知是多么痛苦。而一些相对简单的部门内部协作流程,和协作本身,也没有很好地系统可以承载、实现,最后都变成一个个微信群。每个人都要在忍受无数“信息噪音”的同时,随时留意与自己真正相关的聊天内容。我们都被绑架在工作群聊中。凡此种种,让我觉得信息必然有更好的组织方式,大量日常协作流程也不应该依赖于在群聊中共享信息流。我开始意识到,我们缺少一个真正解决这些问题的工具。

他们对 wolai 的定位是这样的:

  1. 可以更好地组织信息,不论是组织方式还是交互体验;

  2. 可以让每个人都能自由构建流程与应用,满足团队日常工作所需;

  3. 可以进行更好的分享与协作;

  4. 不能是一个呆板严肃的被老板逼着用的办公软件;

  5. 团队或企业可以完成日常 80% 以上的协作,让 Office 回归专业软件,让微信回归即时通讯……

到今天为止,已经累计发布了将近 900 个版本,平均一天发布近 3 次,优化、迭代、完善了无数产品功能;也有了 Windows 、Mac 、Linux 、iOS 、Android 全平台客户端;建立了云服务的空间计划体系,夯实了云端协作平台的基础……终于也算变成了一个有模有样的产品。

卧槽,牛逼,能一年搞得这样,真的很佩服了,尤其还支持 Linux!

而且这个界面设计感,我觉得很不错!

其实我本来想好好介绍一下这个软件的,但是我感觉我自己写了之后,和原来官方写的文章其实也大差不差。而且我感觉人家的这篇文章写的也挺好的。

所以大家可以直接来看看原介绍文章吧:https://zhuanlan.zhihu.com/p/379723832

功能

我就简单说几个让我觉得眼前一亮的功能吧。

界面

大家可以发现,我每次介绍软件都要先评论一下界面,就简单放个官方的图吧:

文章支持 Markdown 绝大多数语法,而且文章上面可以放头图,而且还有专门的 icon,五颜六色的 tag 支持,整个风格是 Material 风格设计,一些边距、图标让人感觉很舒服。

他们自己也说了:

在调性上,区别于千篇一律使用蓝色为主色调的企业应用,我们反其道而行,以暖色为主色调。去除矩形的锋利棱角,用圆润的边角体现 wolai “专业却不失温度”的企业文化和产品理念。

一些文章可以灵活分类组织,每个分类还有单独的图标,井然有序,给单调的列表增加了几分活力有木有!

我爱了。

看看我这篇文章就用 wolai 写的,就看这个图标设计吧,左上角可以选一个图标,支持 Emoji,各种颜色的扁平图标,甚至是动态日历,比如这就是个动态日历:

同时设置完了之后还能显示在文章标题前面,这细节到位了!

全平台同步

另外很重要的当然就是这个全平台的同步了,我看了一下,它支持 Mac、Windows、Android、iPad、iPhone 各种各样的平台,尤其还支持 Linux!这个一上来就把这个好感度拉满了。

而且 Mac 也专门为 M1 芯片做了适配。

很妙!

导入导出

另外我测试了一下,它支持 Markdown 的导入和导出功能,也就是说,只要我有一个 md 的文件,那我可以立马将它转到这个平台里面去,同时他还支持 pdf、md 文件的导出。

这不算什么,我比较欣赏的是 wolai 的一个批量导入的功能,也就是说我可以把我之前所有的 md 文件,然后直接压缩成一个 zip 包。一键上传,wolai 可以解析然后转换,然后所有的文章就导入进来了,牛逼!

编辑历史

还有一个比较特色的功能是它可以保留每一个 Block 的修改历史,那也就是说如果我们想要回滚一些版本的话,是可以非常方便的查看的。这就类似于他比较好地集成了 Git 版本控制工具,但这个功能得高级版才能支持。

情怀

还有个加分项,就是整个团队他对一些细节的把控,甚至可以称作一种情怀。

比如吧,说他们为了呈现团队成员列表,自己还开发了自己的头像小程序,而且甚至还开发了一个头像创作系统,自己捏自己的脸,放一个对比图吧:

卧槽,右边那个我感触太深了,我实在是看不下去哪些乱七八糟的图像混杂排布的效果。wolai 这真的牛逼了!我看到之后太舒服了!

而且这些头像不是死的!这是他们的头像制作小程序,自己可以给自己捏脸,如图所示:

这个我觉得做的确实很有情怀了。

还有一个,为了将传统文化瑰宝融入 wolai ,他们还与故宫博物院取得联系,并获得文物图片的使用授权。只有在 wolai ,我和你才能….使用故宫文物作为页面题头图。

卧槽,情怀拉满了!

细节

另外再说个细节吧,就是这个中英文的混合排版。

我其实对于排版是非常严格的,就是我如果看到一个中文和一个英文单词之间没有一定的间距的话,会觉得很难受。

所以说在公众号排版的时候,中英文中间我都会留一个间距,比如说就中英文之间加一个空格。

不同于 Notion,wolai 它对中英文的排版支持的这个细节把控就做得很到位。比如说我这里敲一个中文和英文,那它会自动的在这两个之间加一个空白,如图所示:

这个真的是很细节了!感动。

有一点需要说明,wolai 在中英文之间插入的间隔并非实际空格,只是样式上的限定。一旦将文字复制或者导出为本地文件,却是没有空格的。

最后

现在我已经用起来了,它可以支持多个工作空间,比如我这里就创建了三个,「进击的Coder」可以作为团队型的空间,可以支持多人协作。另外还有两个空间就专门留给我,一个用来写技术,一个用来写生活,如图所示:

再告诉大家个好消息吧,我在写这篇文章的时候,使用的是 wolai 的个人免费版,它的 Block 数量是有限制的,最多是 5000 个,也就是说,按照一篇文章 100 个 Block 来算,那写 50 篇文章就没 Block 数量了,那就得收费了。

但是!!!从 2021 年 6 月 15 日开始!也就是 wolai 一周年到来之际,个人免费版开始不再限制块儿的数量了,那也就是说我们不管写多少文章,那都不会有限制了!

我当时看到这个消息的时候,看了下日期,6 月 14 日,哦?幸福来得也太突然了吧!

这次我真的不是为这个软件专门打广告的,确实是真心的推荐。

我现在也不能完全承诺,以后我就一定会在这个平台上来管理我的所有的文档。因为我刚开始用,可能还得适应一段时间,要是中途遇到什么 bug 也会看看开发团队的解决问题的态度。

我也不能承诺这软件一定是你中意的,但至少我看到的这个软件提供的一些功能点是我想要的。各个方面是很契合我的需求,而且里面的一些细节确实让我感动满满。

我也不能承诺这个团队以后会不会因为各种原因停止运营和开发这个软件。但我相信,能做出这个产品的团队,能把细节把控到这个地位的团队,也一定不会差了。

最后大家如果想试用看看,可以扫个码注册下:

希望对大家有帮助~

更多精彩内容,请关注我的公众号「进击的Coder」和「崔庆才丨静觅」。

个人随笔

昨天(准确说是前天了),鸿蒙 2.0 发布了,当时没来得及看,睡觉之前看了看回放,说实话这次的更新发布的确让我眼前一亮,甚至自己还有点小心动。因为我现在用的苹果全家桶,所以之前一直对安卓并不太关注,但发布会看到鸿蒙现在建立起的生态还有一些互联的体验,着实让我惊叹一把。甚至我心里都有一点想把自己现有的小米之家的一套以后换成华为全屋智能。

别说,还真有可能哈哈。

扯远了,说回正题。

发布会上宣布了一个消息,那就是现有的很多华为手机都可以开始升级鸿蒙系统,所以一个明显的感觉就是花粉们都开始纷纷尝鲜,从朋友圈就能看得出来大家的期待和热情。

然而,还有一些声音又开始热闹起来了,一些人又开始问了,甚至喷起来了——鸿蒙系统 2.0 到底是不是基于安卓?鸿蒙真无耻,明明基于安卓,还把安卓的名字抹掉叫做鸿蒙。看了这些问题和评论,我心里说有点嗤之以鼻吧又觉得太极端,我就单纯觉得争论这个真的挺无聊的,争论这个有什么意义呢?

看完这篇文章,你可能就更了解我为什么这么说了。

一些渊源

因为众所周知的一些原因,美丽国开始打压华为,断供芯片等关键元器件,而且华为不能再使用谷歌的移动服务。

断供芯片就不说了,说说后者。

谷歌移动服务有一个简称,就是 GMS,英文是 Google Mobile Service,比如 Gmail、Google Play 等等都属于其中。华为不能使用 GMS,这对华为的海外市场可谓是一个巨大的打击。我们国内没法用谷歌的服务自然大家也习以为常了,所以这个变化对国内市场影响不大。然而华为主要消费群体不会局限于国内,国外也是一个非常重要的市场。GMS 之于国外用户就像微信之于国内用户,华为手机没法用 GMS,也就没法用 Google Play,没了 Google Play,那就连应用都没法好好地装了。有人会说,那我把 Google Play 等 App 的 apk 下载下来安装不就好了吗?不行的,没有 GMS,那就相当于就得不到 Google 的认证,所以即使是把 Google Play 的 apk 下载下来也是没法用的。所以,所有 GMS 的生态都没法用了,尤其对于那些已经重度依赖 GMS 的国外用户来说,对买不买华为手机是有非常大的顾虑的。

在去年的第一季度,华为的手机销量在全球是位居第一的,然而今年第一季度,华为的全球手机销量已经跌至第六,被三星、苹果、小米、OPPO、VIVO 赶超了,第五的 VIVO 是 10%,而华为今年第一季度已经跌到 4% 了。

嗯当然对于这种情形,华为也有自己的应对措施了。比如华为打造了 Huawei Mobile Service,即 HMS,但显然这个相比 GMS 还是有很多不成熟的地方,或者让用户从 GMS 迁移到 HMS,也一定是有很多顾虑和成本的。

万般艰难之下,一个重磅消息就诞生了,那就是鸿蒙操作系统的宣布!还记不记得华为宣布鸿蒙的那一天,朋友圈、微博炒的是那么火热,连国家的各个媒体号都在疯狂宣发。为什么?因为当时宣称,这是我们中国人的操作系统!

哇,你就想象吧,那种大国自豪感,虽然说网友们平时天天在网上喷来喷去的,但是鸿蒙宣布的那一天,每个人都怀着一种大国自豪感,显得是那么团结。

后来一段时间,鸿蒙宣布开放源代码,当时记得有一段时间华为在挤牙膏,放出来了一些源码,当时一些热心网友研究之后就炸锅了,这开发工具怎么和 Android Studio 这么像啊?然后开始从 SDK 或者各处去扒安卓的影子,争先恐后去当第一个扒出安卓蛛丝马迹的人。然后一些套壳安卓的声音就不绝于耳了,骂声变得此起彼伏。我当时其实没有太关注这些,也没去深究这些,但当时给我的感觉就是,某些人就开始揪着套壳安卓这个事不放下,喷来喷去。

当时我的态度其实比较坦然,因为我也算懂些安卓,深知从 0 开发出一套安卓系统是一件多么难的事,安卓这发展了十几年才发展成这样子。如果从 0 开始造一个操作系统,即使抛开开发成本,其生态的建立也是极其漫长的过程,如果真有了,而市面上大多数软件都不兼容,微信都没法跑,大批大批的软件运行不了,那谁还会去用呢?几乎是必死的一条路了。所以当时鸿蒙发布会宣布兼容安卓应用的那个时候,我就觉得底层肯定和安卓脱不了干系了,内核肯定基于 Linux,至于多么像,那就得看把安卓改到哪个地步了。

是否基于安卓

好,那下面就来说说,鸿蒙到底是不是基于安卓。

要回答这个问题,我们得首先知道,安卓究竟是什么,要说安卓,就不得不先说一个名字,叫做 AOSP。

AOSP,就是 Android Open Source Project 的简称,翻译过来就是安卓开源项目,官网是 https://source.android.com/。

官网长这样:

AOSP 是开源的,主要是谷歌贡献的,当然其他的厂商也贡献了,比如华为、三星,甚至千千万万普通开发者。

那么 AOSP 和我们常说的安卓有什么关系呢?简单来说,安卓是谷歌在 AOSP 的基础上增加了 GMS(前文提到了),AOSP 是开源的,GMS 是闭源的,二者结合起来就成了现在的安卓系统,最纯正最原生的安卓系统大家可以到谷歌官方下载个 Android Studio 开个虚拟机体验下,现在已经发布到 11 了,网址是:https://developer.android.com/。

OK,但是我们平常用的一些手机的系统和原生安卓也还是有些许不同的,比如小米的 MIUI、三星 OneUI、OPPO 的 ColorOS,锤子的 Smartisan OS,这些都是基于 AOSP 开发的 ROM,而且用也上了 GMS,虽然国内因为某些原因没法用,但它也是有的,况且它们也没有像华为一样被禁。另外小米等公司也大大方方承认这是基于 Android 的,所以你可以看到小米手机的系统里面就写着,MIUI 什么版本,安卓内核什么版本。

OK,那鸿蒙呢?因为安卓 = AOSP + GMS,而 GMS 已经被禁了啊,那何必还称自己基于安卓呢?是吧,那就叫鸿蒙。

是的,就是这样。

所以,AOSP,它是开源的,谷歌放弃了它的所有权。AOSP 人人都能用,人人都能基于它开发。可以认为,AOSP 是安卓和鸿蒙的妈,安卓和鸿蒙是 AOSP 的孩子,只不过不同一个时间段,安卓在 AOSP 基础上多了个 GMS,而鸿蒙没有。

很多人就是像通过 AOSP、安卓、鸿蒙之间的关系想努力证明安卓和鸿蒙是一样的,鸿蒙是套壳安卓的,何必呢?

而且,我们必须要承认,华为的鸿蒙基于 AOSP 做了很多修改,基于鸿蒙的一些设计战略,分布式互联系统的出发点,少不了底层的一些修改和优化。至于难度怎么样?小米、锤子人家都能做成这样,对于华为这么大的公司来说,还会难吗?

而且我相信,将来鸿蒙虽然基于 AOSP 改,但未来也会离 AOSP 越来越远,甚至慢慢面目全非都有可能。

鸿蒙的核心优势

但其实我想说的重点不是上面的内容,不是为鸿蒙和安卓的区分做一个辩驳,因为我认为这是没有意义的。

因为真正有意义的点在于,我们应该关注华为借着鸿蒙,怎样发挥了公司的本身优势,建立了怎样一个基于鸿蒙的巨大生态。

华为不会纠结于到底是不是真正自己从 0 开始实现一个系统,这真的没有意义。华为是站在 AOSP 的基础上,想着怎么把自己的优势发挥出来,怎么将无法使用 GMS 的损失降低,怎么建立一套新的生态体系,怎样重新让华为的市场振作起来,甚至还可以证明,我们中国做的系统也一样可以引起一些可以称之为颠覆性的浪潮。

OK,大家相比也看到了发布会的一些演示了,我觉得华为有一个点做得非常好,那就是充分发挥了自己公司的优势。

华为是什么公司?是一家通信公司,通信技术在世界都是名列前茅,而且华为也一直在着眼于打造物联网全屋智能。

所以,这些碰撞在一起,将鸿蒙定位成一个分布式智能终端操作系统,这就一下子把鸿蒙的局限性从安卓手机这个层面抬高了,格局就不一样了。

华为很聪明,鸿蒙一下子被抬升到这个高度,那自然把名字也扩展到其他的设备上吧。

  • 比如说,华为家的智能手表,其实本来说智能也智能,但没个很正式的名字,那就叫鸿蒙!

  • 比如说,智能家电里面的芯片吧,其实就是个嵌入式的设备,很多厂商都能做啊,智能饮水机、智能燃气灶,智能加湿器,小米家不也做得挺好的吗,也能通过 App 控制,但你说小米家的智能加湿器里面运行的系统叫啥名?说不上来。但华为就不一样了,鸿蒙的定位上来了,格局上来了,那我们就叫它鸿蒙系统啊,名字嘛,就是个叫法而已。

好家伙,这名字一改,那我岂不是就可以对外宣称说,我们鸿蒙系统可以运行在手表,运行在所有的智能 IoT 设备上了,鸿蒙一下子逼格又提升了一个档次,不但格局上来了,技术也显得更牛逼了。鸿蒙还能兼容这么多平台,一听就牛逼吧。

所以,我还是很佩服华为的这个思路的,高!实在是高!

但光有战略不够啊,那得实打实做出点东西才行对吧。

有,这个必须有!而且正好撞枪口上了,通信和物联网,就是华为的优势点所在。

在鸿蒙基础上,华为配合上自家的通信技术的加持,将设备联动做得尽善尽美,基于超级终端这个定义,将所有的设备联系在一起。

  • 比如说,手机和大屏 Pad 的联动,华为把手机在 Pad 的镜像和操作做到了极低的延迟和极高的流畅度,同时加上软件的一些优化,实现数据的共享。

  • 比如说,手机和耳机和电视的联动,通过手机作为中枢,将视频流、音频流的传输和同步做到了极致,实现了无缝的转接体验。

  • 比如说,华为手机、无人机组成的多机位拍摄,将硬件和软件之间的关系解耦,每个终端都能自由选择连接的硬件,这其中也是需要非常强的通信技术。

这就是把自己最牛逼的技术正好最大化地发挥出来了啊!

鸿蒙发布会上提到了两个核心技术:

  • 软时钟同步
  • 抗干扰算法

软时钟同步是通信行业非常重要的技术指标,另外基于 5G 的时钟同步更为重要,该项技术能极大地影响通话体验、音视频的同步效果。华为通信出身,又是 5G 引领者,搞这个自然不在话下了。之于抗干扰算法,不必多说,这属于无线通信的范畴,将自己的技术应用到鸿蒙也是顺理成章了。

所以,最后大家可以看到,华为借着鸿蒙,把自家最牛逼的技术实现了完美的落地,打造了物联网生态,又带动了自家全屋智能家居的发展,拉高了鸿蒙的布局,又证明了鸿蒙的确能行,还能顺便给国家注入一剂强心剂!一举多得。

这才是鸿蒙真正的优势所在。

这才是鸿蒙真正的战略所在。

这才是鸿蒙对消费者、对技术发展、对国家、乃至对世界的意义。

Oh,这时候你再跟我说,鸿蒙是套壳安卓,你看看我还理你嘛?

个人记录

今天看到一个消息,说微信 PC 版最新版本可以刷朋友圈了,还加了许多新功能,简直喜大普奔,我赶紧装来试试。

不过可能让大家觉得稍有遗憾的是,现在发布的只支持 Mac 版,而且处在内测阶段,Windows 版的后续才会发布。

别问我怎么搞到的,看完这篇文章你也能立马获取。

装完之后,打开微信就提示了最新版本的更新界面:

这是 Mac 上的重大更新:

  • 可以刷朋友圈了
  • 看视频号的视频和直播了
  • 可以支持深色模式了

就是主要这么几大更新,我们来一起看看。

首先就是深色模式,这个简直太舒服了,现在很多 Mac 软件都已经适配了深色模式,唯独微信迟迟没有更新,这下终于有了!

我把 Mac 调整到深色主题,然后微信就会跟着变化了。

深色模式是这个样子的:

很完美,以后深夜再也不用面对那明晃晃的白色聊天窗口了。

接下来就是朋友圈,在侧栏出现了这样的小图标:

一点就会弹出一个新的窗口,而不是呈现在右侧,窗口是张这个样子的:

在右上角有刷新按钮和消息按钮,消息按钮点击之后同样以浮窗的形式呈现:

另外朋友圈点赞和评论的操作基本上和手机上是一样的:

小视频播放也不在话下:

嗯,整体感觉还是不错的,以后朋友圈就可以在电脑上看了。

接下来就是视频号,测试了一下转发了一个视频号的内容,显示是这样的:

然后还是以弹窗的形式呈现:

不过只有转发的操作,没法点赞和评论。

然后就是直播,转发的直播在聊天中是这样的:

点开之后显示是这样的:

这个是我的视频号哈,就简单开了一下直播,照的是家里的房顶,没啥实质性的东西,就仅做测试哈。

整体体验下来感觉这个更新还是很给力的,尤其对我有用的就是看朋友圈的功能了。

但是稍微有点遗憾的是,还不支持发朋友圈的功能,希望微信能快快支持。

最后,如果你也想体验一下,请在本公号回复“微信最新版”获取下载地址,大家晚安!

技术杂谈

背景

有时候我会碰到快速搭建测试服务的需求,比如像这样:

搭建一个 HTTP Service,这个服务器可以 run 在本地,也需要公网可以访问,请求该服务可以得到一组自定义的 JSON 数据。不为别的,就为临时快速做点测试用。

这时候我想要以最短的速度完成,比如一分钟就写出来,这时候可以怎么做?

比如大家可能想到了,跑个 Flask 或者 FastAPI,把示例代码改改,然后 Python 一个命令就跑起来了。

比如代码像这样:

1
2
3
4
5
6
7
8
from typing import Optional
from fastapi import FastAPI

app = FastAPI()

@app.get("/")
def read_root():
return {"Hello": "World"}

然后我用命令跑起来:

1
uvicorn main:app --reload

OK,说好的自定义 JSON 就已经完成了。

可是我要加需求了,我要支持跨域访问,怎么做?这时候我可能又要去搜 FastAPI cors 关键字,然后找到 https://fastapi.tiangolo.com/tutorial/cors/ 文档,然后加上类似这样的一些配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from fastapi.middleware.cors import CORSMiddleware

app = FastAPI()

origins = [
"http://localhost.tiangolo.com",
"https://localhost.tiangolo.com",
"http://localhost",
"http://localhost:8080",
]

app.add_middleware(
CORSMiddleware,
allow_origins=origins,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
...

也还行对吧。

那现在我又改需求了,我要返回一张图片怎么办?我要返回一个文件怎么办?我要 HTTPS 访问怎么办?

甚至说,我代码写的不熟怎么办?为了搞这个 API Service 我得花大半个小时,太得不偿失了吧。

毕竟大家都挺忙的。

所以我会想,这些简单的事,为啥要写代码解决啊?难道没有工具通过一些可视化配置来完成吗?

如果你也有这个痛点,请继续向下看。

如果你没有,既然来都来了,客官继续看看嘛…

解决方案

所以现在我的需求是:我想通过一个便捷的工具快速搭建一个 API Server,能配置返回 JSON 或者图片或者文件等等,甚至说动态路由、动态转发等等功能,如果这些步骤还能通过可视化图形界面来搞定就更好了。

来了,今天就给大家推荐一个工具,叫做 Mockoon。

Mockoon 是一个可以通过图形化界面帮我们快速搭建 API 服务的工具,支持数据模拟、路由解析、跨域访问、HTTPS、自定义延时、Docker 等等各种你想要的功能,同时支持支持 Windows、Mac、Linux,页面整体是这样子的:

这布局,和 PostMan 有异曲同工之妙啊。

比如左侧我们可以配置一个个请求列表,点进去可以在右侧配置详情,比如配置是 GET 还是 POST 请求,path 是什么,Response Body 是什么,Response Headers 是什么,另外还有一些规则和基础设置。

另外在最上面我可以配置运行的 host 和 port,然后左上角还有一个运行按钮,一点就相当于启动了 Server 了,启动之后按钮就会变成红色,再按一下就会停止,比如这里我就配置了运行在本地 3894 端口:

然后我修改下 Body:

1
2
3
4
5
6
7
{
"data": [
{"id": 1, "name": "Picture3", "url": "https://qiniu.cuiqingcai.com/l4ol8.jpg"},
{"id": 2, "name": "Picture2", "url": "https://qiniu.cuiqingcai.com/zy2w3.jpg"},
{"id": 3, "name": "Picture1", "url": "https://qiniu.cuiqingcai.com/v10oo.jpg"}
]
}

这里我返回一个 JSON 格式的列表,包含了三个字段。

然后接下来我要配置跨域访问,就加一个 Response Header:

1
Access-Control-Allow-Origin: '*'

然后点击左上角的运行按钮就成了。

Mockoon 还提供了快捷访问的功能,接着点右上角的打开按钮:

浏览器就打开了,然后数据就看到了:

咔咔咔,就这样,我们通过非常简单的可视化配置就完成了 API Server 的搭建,熟练的话一分钟就完成了。

另外还有太多功能,比如 HTTPS、多请求处理、日志、路由、模板配置这里就不再一一叙述了,用到的时候查文档就好啦:

  • Cors:https://mockoon.com/docs/latest/cors/
  • HTTPS:https://mockoon.com/docs/latest/https/
  • Import/Export Data:https://mockoon.com/docs/latest/import-export-data/
  • Mutiple Response: https://mockoon.com/docs/latest/multiple-responses/
  • Proxy Mode: https://mockoon.com/docs/latest/proxy-mode/
  • Request logging: https://mockoon.com/docs/latest/requests-logging/
  • Response Headers: https://mockoon.com/docs/latest/response-headers/
  • Routing: https://mockoon.com/docs/latest/routing/
  • Templating: https://mockoon.com/docs/latest/templating/

另外 Mockoon 还支持命令行,比如通过 mockoon-cli 就可以快速创建一个 API Server,如图所示:

img

命令行的使用和安装可以参考:https://github.com/mockoon/cli#installation

以上便是这个工具的简单介绍,更多功能等待你的探索!

技术杂谈

在这里介绍一个工具,使用它我们可以非常方便地使用 Python 下载 Youtube 的视频,叫做 pytube。

利用它我们可以实现如下功能:

  • 支持 progresive 和 DASH 视频流的下载
  • 支持下载完整的播放列表
  • 可以处理下载进行中和下载完成的回调
  • 提供命令行直接执行下载
  • 支持下载字幕
  • 支持将字幕输出为 srt 格式
  • 获取视频缩略图

下面我们就来详细了解一下它的使用方法。

安装

首先看下安装过程,安装非常简单,只需要使用 pip3 安装即可,命令如下:

1
pip3 install pytube

或者直接源码安装也行:

1
pip3 install git+https://github.com/pytube/pytube

安装完成之后就可以使用 pytube 命令了。

使用

这里先介绍两个最常见的用法,那就是直接使用 pytube 命令,它可以用来下载单个 Yotube 视频或者视频列表。

比如这是一个视频 https://youtube.com/watch?v=2lAe1cqCOXo,截图如下:

我们可以直接使用命令下载:

1
pytube https://www.youtube.com/watch?v=2lAe1cqCOXo

很快视频就能被下载下来了:

1
2
3
Loading video...
YouTube Rewind 2019 For the Record YouTubeRewind.mp4 | 83 MB
↳ |█████████ | 21.4%

同样地,pytube 还能下载播放列表,比如这是一个播放列表 https://www.youtube.com/playlist?list=PLS1QulWo1RIaJECMeUT4LFwJ-ghgoSH6n,截图如下:

使用 pytube 同样可以轻松下载:

1
pytube https://www.youtube.com/playlist?list=PLS1QulWo1RIaJECMeUT4LFwJ-ghgoSH6n

它可以解析播放列表,然后一个个下载下来:

1
2
3
4
5
6
7
8
9
10
11
12
13
Loading playlist...
Python Tutorial for Beginners 1 - Getting Started and Installing Python (For Absolute Beginners).mp4 | 63 MB
↳ |████████████████████████████████████████████| 100.0%
Python Tutorial for Beginners 2 - Numbers and Math in Python.mp4 | 11 MB
↳ |████████████████████████████████████████████| 100.0%
Python Tutorial for Beginners 3 - Variables and Inputs.mp4 | 15 MB
↳ |████████████████████████████████████████████| 100.0%
Python Tutorial for Beginners 4 - Built-in Modules and Functions.mp4 | 16 MB
↳ |████████████████████████████████████████████| 100.0%
Python Tutorial for Beginners 5 - Save and Run Python files py.mp4 | 37 MB
↳ |████████████████████████████████████████████| 100.0%
Python Tutorial for Beginners 6 - Strings.mp4 | 78 MB
↳ |███████████████████████████████████ | 79.6%

当然除了默认的命令配置,还可以支持查看 list,查看字幕,筛选语言等等,具体的命令如下:

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
31
32
33
34
35
36
37
38
usage: pytube [-h] [--version] [--itag ITAG] [-r RESOLUTION] [-l] [-v]
[--logfile LOGFILE] [--build-playback-report] [-c CAPTION_CODE]
[-lc] [-t TARGET] [-a [AUDIO]] [-f [FFMPEG]]
[url]

Command line application to download youtube videos.

positional arguments:
url The YouTube /watch or /playlist url

optional arguments:
-h, --help show this help message and exit
--version show program's version number and exit
--itag ITAG The itag for the desired stream
-r RESOLUTION, --resolution RESOLUTION
The resolution for the desired stream
-l, --list The list option causes pytube cli to return a list of
streams available to download
-v, --verbose Verbosity level, use up to 4 to increase logging -vvvv
--logfile LOGFILE logging debug and error messages into a log file
--build-playback-report
Save the html and js to disk
-c CAPTION_CODE, --caption-code CAPTION_CODE
Download srt captions for given language code. Prints
available language codes if no argument given
-lc, --list-captions List available caption codes for a video
-t TARGET, --target TARGET
The output directory for the downloaded stream.
Default is current working directory
-a [AUDIO], --audio [AUDIO]
Download the audio for a given URL at the highest
bitrate availableDefaults to mp4 format if none is
specified
-f [FFMPEG], --ffmpeg [FFMPEG]
Downloads the audio and video stream for resolution
providedIf no resolution is provided, downloads the
best resolutionRuns the command line program ffmpeg to
combine the audio and video

更多详细的说明可以参考官方文档:https://python-pytube.readthedocs.io/en/latest/user/cli.html

代码使用

当然除了这些,pytube 还支持以 Python 编程的方式来进行下载,同时提供了便捷的链式操作,比如这段代码:

1
2
3
4
5
6
7
8
9
>>> from pytube import YouTube
>>> YouTube('https://youtu.be/2lAe1cqCOXo').streams.first().download()
>>> yt = YouTube('http://youtube.com/watch?v=2lAe1cqCOXo')
>>> yt.streams
... .filter(progressive=True, file_extension='mp4')
... .order_by('resolution')
... .desc()
... .first()
... .download()

这里大家可以看到,要使用 pytube,只需要导入其中的 Youtube 这个类,然后传入 URL 声明 Youtube 对象就好了。接着我们可以直接调用其 streams 方法获取所有的视频源,然后可以通过 first 或者 filter 或者 order 等进行排序或筛选等处理,然后最后调用 download 方法就可以执行下载了。

下面我们来剖析一下具体是怎么回事。

首先我们来声明一下 YouTube 对象:

1
2
3
4
>>> from pytube import YouTube
>>> yt = YouTube('https://youtu.be/2lAe1cqCOXo')
>>> yt
<pytube.__main__.YouTube object at 0x7f88901e9890>

然后看看 streams 是什么:

1
2
3
4
>>> yt.streams
[<Stream: itag="18" mime_type="video/mp4" res="360p" fps="24fps" vcodec="avc1.42001E" acodec="mp4a.40.2" progressive="True" type="video">, <Stream: itag="22" mime_type="video/mp4" res="720p" fps="24fps" vcodec="avc1.64001F" acodec="mp4a.40.2" progressive="True" type="video">, <Stream: itag="137" mime_type="video/mp4" res="1080p" fps="24fps" vcodec="avc1.640028" progressive="False" type="video">, <Stream: itag="248" mime_type="video/webm" res="1080p" fps="24fps" vcodec="vp9" progressive="False" type="video">, <Stream: itag="399" mime_type="video/mp4" res="1080p" fps="24fps" vcodec="av01.0.08M.08" progressive="False" type="video">, <Stream: itag="136" mime_type="video/mp4" res="720p" fps="24fps" vcodec="avc1.4d401f" progressive="False" type="video">, <Stream: itag="247" mime_type="video/webm" res="720p" fps="24fps" vcodec="vp9" progressive="False" type="video">, <Stream: itag="398" mime_type="video/mp4" res="720p" fps="24fps" vcodec="av01.0.05M.08" progressive="False" type="video">, <Stream: itag="135" mime_type="video/mp4" res="480p" fps="24fps" vcodec="avc1.4d401e" progressive="False" type="video">, <Stream: itag="244" mime_type="video/webm" res="480p" fps="24fps" vcodec="vp9" progressive="False" type="video">, <Stream: itag="397" mime_type="video/mp4" res="480p" fps="24fps" vcodec="av01.0.04M.08" progressive="False" type="video">, <Stream: itag="134" mime_type="video/mp4" res="360p" fps="24fps" vcodec="avc1.4d401e" progressive="False" type="video">, <Stream: itag="243" mime_type="video/webm" res="360p" fps="24fps" vcodec="vp9" progressive="False" type="video">, <Stream: itag="396" mime_type="video/mp4" res="360p" fps="24fps" vcodec="av01.0.01M.08" progressive="False" type="video">, <Stream: itag="133" mime_type="video/mp4" res="240p" fps="24fps" vcodec="avc1.4d4015" progressive="False" type="video">, <Stream: itag="242" mime_type="video/webm" res="240p" fps="24fps" vcodec="vp9" progressive="False" type="video">, <Stream: itag="395" mime_type="video/mp4" res="240p" fps="24fps" vcodec="av01.0.00M.08" progressive="False" type="video">, <Stream: itag="160" mime_type="video/mp4" res="144p" fps="24fps" vcodec="avc1.4d400c" progressive="False" type="video">, <Stream: itag="278" mime_type="video/webm" res="144p" fps="24fps" vcodec="vp9" progressive="False" type="video">, <Stream: itag="394" mime_type="video/mp4" res="144p" fps="24fps" vcodec="av01.0.00M.08" progressive="False" type="video">, <Stream: itag="140" mime_type="audio/mp4" abr="128kbps" acodec="mp4a.40.2" progressive="False" type="audio">, <Stream: itag="249" mime_type="audio/webm" abr="50kbps" acodec="opus" progressive="False" type="audio">, <Stream: itag="250" mime_type="audio/webm" abr="70kbps" acodec="opus" progressive="False" type="audio">, <Stream: itag="251" mime_type="audio/webm" abr="160kbps" acodec="opus" progressive="False" type="audio">]
>> type(yt.streams)
<class 'pytube.query.StreamQuery'>

可以看到这里 streams 是一个 StreamQuery 对象,然后输出出来看起来像是一个列表,其中包含了一个个 stream 对象。

所以,StreamQuery 就是我们需要重点关注的对象了。

比如接下来我们使用 filter 或者 order_by 方法进行处理:

1
2
>>> type(yt.streams.filter(file_extension='mp4'))
<class 'pytube.query.StreamQuery'>

可以看到它依然还是一个 StreamQuery 对象。

然后根据分辨率进行排序:

1
2
>>> type(yt.streams.filter(file_extension='mp4').order_by('resolution'))
<class 'pytube.query.StreamQuery'>

还是一样,返回的还是 StreamQuery 对象。

这下明白为什么它可以进行链式操作了吧,因为每次 filter 或者 order_by 对象返回的依然还是 StreamQuery 对象,依然还是可以调用对应的方法的。

不过也不是每一个都是支持链式操作的,比如接下来我们对 StreamQuery 对象调用 first 方法:

1
2
3
4
>>> yt.streams.first()
<Stream: itag="18" mime_type="video/mp4" res="360p" fps="24fps" vcodec="avc1.42001E" acodec="mp4a.40.2" progressive="True" type="video">
>>> type(yt.streams.first())
<class 'pytube.streams.Stream'>

看到这里返回的就是单个 Stream 了。

对于 Stream 对象,我们最常用的就是 download 方法了。

1
yt.streams.first().download()

调用完毕之后,视频就会下载在运行目录中。

以上我们就介绍了利用命令行和代码进行视频下载的过程。

更多

当然这个库还有很多强大的功能,都在文档 https://python-pytube.readthedocs.io/en/latest/ 写得很清楚了,这里带大家稍微看下。

比如获取视频的属性:

1
2
>>> yt.title
YouTube Rewind 2019: For the Record | #YouTubeRewind

获取视频的缩略图:

1
2
>>> yt.thumbnail_url
'https://i.ytimg.com/vi/2lAe1cqCOXo/maxresdefault.jpg'

在初始化的时候设置处理方法或者设置代理:

1
2
3
4
5
6
>>> yt = YouTube(
'http://youtube.com/watch?v=2lAe1cqCOXo',
on_progress_callback=progress_func,
on_complete_callback=complete_func,
proxies=my_proxies
)

关于 filter 的一些用法,可以参考 https://python-pytube.readthedocs.io/en/latest/user/streams.html#filtering-streams,比如说过滤只保留有音频的流媒体:

1
2
3
4
5
>>> yt.streams.filter(only_audio=True)
[<Stream: itag="140" mime_type="audio/mp4" abr="128kbps" acodec="mp4a.40.2" progressive="False" type="audio">,
<Stream: itag="249" mime_type="audio/webm" abr="50kbps" acodec="opus" progressive="False" type="audio">,
<Stream: itag="250" mime_type="audio/webm" abr="70kbps" acodec="opus" progressive="False" type="audio">,
<Stream: itag="251" mime_type="audio/webm" abr="160kbps" acodec="opus" progressive="False" type="audio">]

保留 mp4 后缀的视频:

1
2
3
4
5
6
7
>>> yt.streams.filter(file_extension='mp4')
[<Stream: itag="18" mime_type="video/mp4" res="360p" fps="30fps" vcodec="avc1.42001E" acodec="mp4a.40.2" progressive="True" type="video">,
<Stream: itag="22" mime_type="video/mp4" res="720p" fps="30fps" vcodec="avc1.64001F" acodec="mp4a.40.2" progressive="True" type="video">,
<Stream: itag="137" mime_type="video/mp4" res="1080p" fps="30fps" vcodec="avc1.640028" progressive="False" type="video">,
...
<Stream: itag="394" mime_type="video/mp4" res="None" fps="30fps" vcodec="av01.0.00M.08" progressive="False" type="video">,
<Stream: itag="140" mime_type="audio/mp4" abr="128kbps" acodec="mp4a.40.2" progressive="False" type="audio">]

比如获取字幕:

1
2
3
>>> yt = YouTube('http://youtube.com/watch?v=2lAe1cqCOXo')
>>> yt.captions
{'ar': <Caption lang="Arabic" code="ar">, 'zh-HK': <Caption lang="Chinese (Hong Kong)" code="zh-HK">, 'zh-TW': <Caption lang="Chinese (Taiwan)" code="zh-TW">, ...

这里各国语言的字幕几乎都有。

另外还可以把字幕打印出来,比如输出 srt 格式:

1
2
3
4
5
6
7
8
9
10
>>> caption = yt.captions.get_by_language_code('en')
>>> print(caption.generate_srt_captions())
1
00:00:10,200 --> 00:00:11,140
K-pop!

2
00:00:13,400 --> 00:00:16,200
That is so awkward to watch.
...

对于播放列表的处理,比如新建 Playlist 对象,然后取出每一个视频的第一个视频流并下载:

1
2
3
4
>>> from pytube import Playlist
>>> p = Playlist('https://www.youtube.com/playlist?list=PLS1QulWo1RIaJECMeUT4LFwJ-ghgoSH6n')
>>> for video in p.videos:
>>> video.streams.first().download()

另外还有一些异常处理机制:

1
2
3
4
5
6
7
8
9
10
11
>>> from pytube import Playlist, YouTube
>>> playlist_url = 'https://youtube.com/playlist?list=special_playlist_id'
>>> p = Playlist(playlist_url)
>>> for url in p.video_urls:
... try:
... yt = YouTube(url)
... except VideoUnavailable:
... print(f'Video {url} is unavaialable, skipping.')
... else:
... print(f'Downloading video: {url}')
... yt.streams.first().download()

总之,使用这个库,我们不仅可以使用命令行方便地下载 Youtube 视频和播放列表,还可以使用代码灵活地控制,一举两得!

个人记录

今天还是照常上班,和以往没有太多不同。

然而,办公室里面有位同事一句话惊动了周围所有同事——我们今年又多了五天假期?!

听罢,同事们像是刚中了彩票一样,一个个高兴得像个孩子,但还不知道具体咋回事。

:哪啊?哪说的啊?

:快看看邮件啊

:哪封邮件啊?

:就今天上午刚发的公司全员邮件啊!标题叫 Announcing Wellbeing Days

:好家伙,你不说我还真没注意

其实我也没注意,然后开始翻邮件,果不其然,找到了!

标题很明显了:

Announcing Wellbeing Days

具体的邮件内容不详细说了,总体意思就是说,现在全球新冠疫情严重,大家都面临非常多的压力,其中有一句关键的信息:

Thus, I am excited to announce five new paid WellbeingDays to support you.

是的,为此公司决定 2021 年给每个员工都加五天全薪“幸福关怀假”。

巨硬牛逼!

然后我又详细看了看,这个针对哪些员工呢?

2021 年 9 月 30 之前入职的员工都可以获得这五天假期,2021 年 9 月 30 到 12 月 1 日入职的员工,可以获得三天假期。

今年我的年假已经变成:

去年留到今年的还有 13 天,今年 15 天还没用,再加上 5 天,33 天年假了。

简直是良心了,这下我终于也体验到了一会“别人公司”的幸福感。

想来的,简历可以砸过来了。

技术杂谈

给大家介绍一个非常实用的工具,有了它,我们可以在几秒之内用 VS Code 打开 GitHub 上的任意一个 Repo,无需 Clone,速度飞快。

用法也十分简单而且好记,下面给大家介绍下。

介绍

比如这里是 Scrapy 的仓库:https://github.com/scrapy/scrapy,用 GitHub 打开是这样的:

GitHub仓库

看代码的时候我们可能需要一个个点进去,速度慢而且感觉不太方便。

为此大家可能安装了一些 Chrome 插件,比如比较火的是 Octotree,安装之后效果是这样的:

Octotree效果

安装这个插件之后,在网页左侧会出现一个文件树方便我们快速定位文件。然鹅,个人感觉总不是真正想要的那种味道。

现在好了。

这时候,我们只需要在网址 github 后面加上 1s,变成:https://github1s.com/scrapy/scrapy

没错,就是这么简单好记。

访问之后,就可以看到这样的页面:

是的没错,这就在浏览器中打开了一个在线版的 VS Code,并打开了 Scrapy 的源代码,看代码就非常方便了。

无需克隆,无需任何配置,打开飞速,代码高亮!

一些快捷键也是和 VS Code 一样的,比如 Cmd/Ctrl + P,就可以快速找到一个文件:

Cmd/Ctrl + F 搜索:

不过我试了一下全局搜索貌似只能限制在一个文件内,不知道是哪里设置的问题。

有的朋友可能会问,代码能修改吗?

答案是不能,代码都是以只读模式打开的,也就是我们只能看,没法改。不过这也合情合理,毕竟任意 Repo 都能打开,改了又存到哪里呢?

原理

另外有的朋友可能好奇这个的实现原理是怎样的,我扒了一下源码,看到作者是这么介绍的:

Github1s is based on VS Code 1.52.1 now. VS Code can be built for a browser version officially. I also used the code and got inspired by Code Server.

Thanks to the very powerful and flexible extensibility of VS Code, we can easily implement a VS Code extension that provides the custom File IO ability using FileSystemProvider API. There is an official demo named vscode-web-playground which shows how it is used.

On the other hand, GitHub provides the powerful REST API that can be used for a variety of tasks which includes reading directories and files for sure.

According to the above, obviously, the core concept of GitHub1s is to implement a VS Code Extension (includes FileSystemProvider) using GitHub REST API.

We may switch to the GitHub GraphQL API for more friendly user experience in the future, thanks to @xcv58 and @kanhegaonkarsaurabh. See details at Issue 12.

GitHub1s is a purely static web app (because it really doesn’t need a backend service, does it?). So we just deploy it on GitHub Pages now (the gh-pages branch of this repository), and it is free. The service of GitHub1s could be reliable (GitHub is very reliable) because nobody needs to pay the web hosting bills.

总的来说,GitHub1s 这个仓库是基于 VS Code 构建的,灵感来源于 Code Server 这个 Repo,地址为:https://github.com/cdr/code-server,这个就是一个 Online 版的 VS Code。

那作者基于这个做了什么事呢?

他基于 VS Code 提供的 FileSystemProvider API 对接了 GitHub 的 REST API 实现了这些功能。其中前者是 VS Code 提供的,可以提供文件读写操作,当然读写在线文件也是没问题的了;而后者是 GitHub 提供的,通过 REST API 可以获取 Repo 的文件夹或者某个文件。

如此,GitHub1s 就诞生了。

知道了原理之后,我们也可以自己把 GitHub1s 代码下载下来,改写一下,扩展一些功能:比如解除只读限制,保存的时候直接存储到自己的 Repo 等。

具体的修改和开发流程可以参见:https://github.com/conwnet/github1s/blob/master/docs/guide.md#development

访问频率限制

另外作者也提到了一点:

For unauthenticated requests, the rate limit allows for up to 60 requests per hour. Unauthenticated requests are associated with the originating IP address, and not the user making requests.

对于未授权的请求,API 的请求频率是有限制的,每个 IP 每个小时访问限制是 60 次,所以用着用着就容易超限制了,可能就打不开文件了。

这里的频率限制我解读下,有朋友可能好奇,因为这个网站是 github1s.com 来 serve 的,所以请求是不是都是 github1s.com 这个服务器发出来的呢?所以不同的人其实都是用的一个 IP?

其实不是的,我观察了一下网络请求,是当前网页直接请求了 GitHub 的 API 实现的,所以 IP 就是我们自己客户端的真实 IP,网络请求如下所示:

但是请求 GitHub 的 API 没有跨域问题吗?

没有,这是因为 GitHub API 设置了解除跨域访问,Response Headers 里面可以看到:

1
access-control-allow-origin: *

所以任何网站都可以直接请求 GitHub 的 API。

OK,说回解除频率限制的问题:

For API requests using Basic Authentication or OAuth, you can make up to 5,000 requests per hour.

这里说如果登录了,每小时就可以提高到 5000 次请求了。

怎么设置呢?

可以在 github1s.com 打开侧栏的 OAuth 设置,如图所示:

image-20210215212607189

然后点击 Generate New OAuth Token,跳到自己的 GitHub Setting 页面,生成一个 Token 就好了,如图所示:

好了之后贴回来就 OK 了,这样频率限制就解除了,爽歪歪。

彩蛋

另外我还发现了有一个配套的 Chrome 插件,也叫 GitHub1s,大家可以自己搜索安装:

安装完成之后,每个 GitHub Repo 都会自动多出一个绿色按钮,如图所示:

点击之后就直接跳转到刚才所说的 github1s.com 打开这个 Repo 了,简直不要太方便!

另外还有另外一个开发者写的插件,效果是这样的:

感觉这个还是挺小巧精致的,可以来这里下载:https://github.com/2293736867/Github1sExtension

总结

好了,以后看 GitHub 代码就有新神器了,大家快去试试看吧~

技术杂谈

事情是这样的,最近发现我的博客 cuiqingcai.com 的评论功能出现了问题,登录功能不好用了。经过一番排查,我找到了一些解决方案,在这里记录一下问题排查过程。

另外这个排查过程中可以总结出一些思路,大家如果碰到类似的问题,也可以按照类似的思路来排查。

友情提示:大家一定要读到最后或者直接拉到最后,最后内容的可能对你更有价值。

起因

我的博客最近刚换上了 hexo 框架,于是评论功能就换成了 Gitalk。但最近发现登录功能不好用了,点击使用 GitHub 登录总是失败。

就点击这个按钮的时候,始终登录不上去:

image-20210212223917778

于是我就想着手解决一下这个问题。

思路

这里就记录一下我在排查过程中碰到的一些坑和解决思路。

首先,登录失败的问题,第一时间应该去排查的就是网络请求,打开控制台,查看 Network 面板,出现类似的结果:

image-20210212224407613

网络请求直接 403 了,拿不到 token 了,于是就登录不上了。

观察下,这个链接 cors-anywhere 似乎是用来解决跨域限制的,后面还跟了一个 GitHub 的 Access Token 获取地址,那没跑了,前面这个就是一个反向代理,后面是真实的请求 URL。

OK,看着这个也没啥思路啊,然后接着怎么办?

那就接着去搜这个 cors-anywhere.herokuapp.com,因为 herokuapp 很眼熟嘛,就是一个公用的网址 Host 平台,类似于 AWS、Azure 之类的,那么前面这个可能包含某些信息。万一是开源的那就好办了。

接着搜,cors-anywhere,然后就搜到了这个:https://github.com/Rob--W/cors-anywhere

介绍如下:

CORS Anywhere is a NodeJS proxy which adds CORS headers to the proxied request.

The url to proxy is literally taken from the path, validated and proxied. The protocol part of the proxied URI is optional, and defaults to “http”. If port 443 is specified, the protocol defaults to “https”.

This package does not put any restrictions on the http methods or headers, except for cookies. Requesting user credentials is disallowed. The app can be configured to require a header for proxying a request, for example to avoid a direct visit from the browser.

真是一个开源框架,和我猜的一样,就是一个解决跨域问题而生的反向代理。

然后我就在它的 README 中看到了这个:

image-20210212224946845

好家伙,这不就是我刚才用到的链接吗?

那肯定是这个玩意出了什么毛病。

咋看呢?这个果断就是找 Issue 了:

image-20210212225114466

一看,太明显了:

PSA: Public demo server (cors-anywhere.herokuapp.com) will be very limited by January 2021, 31st

意思就是从今年 1.31 开始这个网站的访问会受限,点进去看看:

The demo server of CORS Anywhere (cors-anywhere.herokuapp.com) is meant to be a demo of this project. But abuse has become so common that the platform where the demo is hosted (Heroku) has asked me to shut down the server, despite efforts to counter the abuse (rate limits in #45 and #164, and blocking other forms of requests). Downtime becomes increasingly frequent (e.g. recently #300, #299, #295, #294, #287) due to abuse and its popularity.

To counter this, I will make the following changes:

  1. The rate limit will decrease from 200 (#164) per hour to 50 per hour.
  2. By January 31st, 2021, cors-anywhere.herokuapp.com will stop serving as an open proxy.
  3. From February 1st. 2021, cors-anywhere.herokuapp.com will only serve requests after the visitor has completed a challenge: The user (developer) must visit a page at cors-anywhere.herokuapp.com to temporarily unlock the demo for their browser. This allows developers to try out the functionality, to help with deciding on self-hosting or looking for alternatives.

好吧,意思就是说这个网站本来是演示用的,但是现在已经被滥用了,然后从 1.31 开始用户手动必须手动先访问这个网站获取临时的访问权限,然后才能使用。另外推荐开发者自己来维护一个网站。

接着下面的评论第一个就更滑稽了:

image-20210212225414735

这个人直接艾特了 gitalk,哈哈哈,因为 Gitalk 就如刚才所说的那样,也用了这个。

那就顺便去 Gitalk https://github.com/gitalk/gitalk,逛一下 issue,看看是不是也有人遇到了同样的问题,果不其然了:

image-20210212225919986

最近几个 issue 都是关于 403 的,真热闹。

点进去看看,有个大收获,里面有个好心人说:

这次直接去嫖了一个CORS proxy,把gitalk.js的6794行改为 proxy: ‘https://netnr-proxy.cloudno.de/https://github.com/login/oauth/access_token‘, 就可以了。具体能用多久我也没普,且用且珍惜。

真是得来全不费功夫,本来还想着自己部署着,这次那就换了就行了。

然而,这样不行,得需要改 gitalk.js 的源码,并不太好吧。

好,这时候就遇到了一个问题,要修改某些开源软件的源码应该怎么办?

首选的思路当然不是硬改,改了之后还要自己 host 一个新的 js 文件,那显然是很费精力的。

其实一半程序在编写的时候应该是预留一些接口和配置的,我们应该能很轻易地通过某些配置就能实现某些配置的复写。

那就接着看看吧,既然要改,那就得先看看 Gitalk 是怎么用的吧。

看文档,Gitalk 调用方式如下:

1
2
3
4
5
6
7
8
9
10
11
const gitalk = new Gitalk({
clientID: 'GitHub Application Client ID',
clientSecret: 'GitHub Application Client Secret',
repo: 'GitHub repo', // The repository of store comments,
owner: 'GitHub repo owner',
admin: ['GitHub repo owner and collaborators, only these guys can initialize github issues'],
id: location.pathname, // Ensure uniqueness and length less than 50
distractionFreeMode: false // Facebook-like distraction free mode
})

gitalk.render('gitalk-container')

看来这个在声明的时候是有参数的,那刚才 URL 配置没看到在哪里配啊,既然如此,那就看看 Gitalk 这个对象支持多少参数吧。

接着就去找 Gitalk 的构造参数说明,找到这么一个:

  • proxy:String

Default: https://cors-anywhere.herokuapp.com/https://github.com/login/oauth/access_token.

果然找到了,所以这里如果我们要修改,那就改 proxy 参数就行了,初始化 Gitalk 的时候复写掉 proxy 就行。

OK,基本思路有了,那我怎么改到我的源码里呢?

我的博客是基于 Hexo 的 Next 主题的,根据经验,Gitalk 是 Next 主题自带的,所以 Gitalk 的声明应该就在 Next 主题源码里面。

那怎么找呢?

这时候就需要借助于一些搜索技巧了,搜什么?既然要用 Gitalk,那一定有刚才初始化的调用,那就搜 Gitalk 这个关键字就行了。另外还需要缩小一下搜索范围。

于是我就把范围限定到了 next 主题目录,搜索 Gitalk。

image-20210212230931998

简直不要太舒服,一搜就有了,文件是 themes/next/layout/_third-party/comments/gitalk.swig。

这里我们只需要把 proxy 参数加上不就行了,值是什么呢?仿照写就行了,配置风格保持统一,那就加一条:

1
proxy       : '{{ theme.gitalk.proxy }}',

OK,那这个配置的值很明显是主题配置文件,那就把配置文件里面加上 proxy 这个参数就好了。

找到 themes/next/_config.yml,添加行:

1
proxy: https://netnr-proxy.cloudno.de/https://github.com/login/oauth/access_token

结果如下:

image-20210212231224403

好了,大公告成!

重新部署 Hexo,现在评论又能重新使用了,问题就解决了!

测试地址:https://cuiqingcai.com/message/,大家来给我留言吧~

好了,这就是我排查问题的整个过程,做一下记录。

经验

另外,其实这篇文章的用意不仅仅是单纯解决这个问题,因为这个问题大家可能并没有遇到过,因此这个解决方案仅仅是给极少数遇到这个问题的朋友提供的。

但是,这并不代表这篇文章没有价值,因为其中有的思路是通用的,在这里稍微做一下总结,希望对大家有帮助:

  • 当遇到网页功能异常的时候,排查问题就主要看两个——控制台、网络请求,这里面往往能找到主要问题。
  • 结合一些基本知识进行合理的推断,比如刚才我就推断了 cors- anywhere 的作用并结合 herokuapp 推断这个可能还会是个公用的服务。
  • 当碰到没有思路或者不确定的时候,去谷歌它!不要百度,另外还可以在 GitHub 或者 Gitee 上搜。
  • 如果找到对应的 GitHub 仓库,Issue 区往往能找到一些有效答案,比如刚才我就在 Issue 区找到了一个可替代的 cors 网站。
  • 修改代码功能的时候要想着尽量复写,也就是 overwrite,而不是直接改,前者更具有灵活性,而且某些情况下会省去一些麻烦。
  • 复写的时候去找一些参数配置,比如找一些初始化参数、默认参数配置,看看能否实现改写的需求。
  • 找不到入口的时候善用全局搜索功能,比如刚才 Gitalk 找哪里调用的时候,就直接全局搜索。
  • 根据功能限制某些搜索范围,比如刚才我就知道 Gitalk 这个功能是 Next 主题提供的,所以我就直接限制搜索范围是 Next 主题的源码。
  • 以上步骤多尝试,熟能生巧。

技术杂谈

最近遇到了一个问题,就是 Vue.js 中的 prop 如何实现双向绑定比较好。

之前我都是把 prop 传递到子组件,然后子组件里面直接把 prop 直接改了,这样虽然能把结果反映到父组件,但并不是一个很好的解决方案。

比如我就经常遇到这样的 Warning:

Avoid mutating a prop directly since the value will be overwritten…

实际上,用事件传递的方式把修改从子组件传到父组件是比较不错的方式,这就需要子组件 $emit 一个事件,然后父组件监听这个事件,然后将接收到的值修改就好了。

官方文档的说明如下:

在有些情况下,我们可能需要对一个 prop 进行“双向绑定”。不幸的是,真正的双向绑定会带来维护上的问题,因为子组件可以变更父组件,且在父组件和子组件都没有明显的变更来源。

这也是为什么我们推荐以 update:myPropName 的模式触发事件取而代之。举个例子,在一个包含 title prop 的假设的组件中,我们可以用以下方法表达对其赋新值的意图:

1
this.$emit('update:title', newTitle)

所以这里推荐的就是使用 update:propName 来实现。

这样的话,父组件原生的写法如下:

1
2
3
4
<text-document
v-bind:title="doc.title"
v-on:update:title="doc.title = $event"
></text-document>

这里 prop 的名字叫做 title,子组件需要接收到这个值,然后各种操作进行修改都没关系。

比如子组件可以这么写:

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
<template>
<el-input v-model="titleData"></el-input>
</template>

<script>
export default {
name: "Child",
props: {
title: {
type: String,
default: "",
}
},
data() {
return {
titleData: this.title
}
},
watch: {
titleData: {
handler(val) {
this.$emit('update:title', val)
}
}
}
}
</script>

这样当 titleData 更新的时候,父组件的 title 就更新了。

这时候父组件可以简写为:

1
<text-document v-bind:title.sync="doc.title"></text-document>

注意带有 .sync 修饰符的 v-bind 不能和表达式一起使用 (例如 v-bind:title.sync=”doc.title + ‘!’” 是无效的)。取而代之的是,你只能提供你想要绑定的 property 名,类似 v-model

另外当我们用一个对象同时设置多个 prop 的时候,也可以将这个 .sync 修饰符和 v-bind 配合使用:

1
<text-document v-bind.sync="doc"></text-document>

这样会把 doc 对象中的每一个 property (如 title) 都作为一个独立的 prop 传进去,然后各自添加用于更新的 v-on 监听器。

v-bind.sync 用在一个字面量的对象上,例如 v-bind.sync=”{ title: doc.title }”,是无法正常工作的,因为在解析一个像这样的复杂表达式的时候,有很多边缘情况需要考虑。

以上,完毕。

技术杂谈

写这篇文章的缘由来自看到了知乎上的一个问题——在 GitHub 上保持 365 天全绿是一种怎样的体验?

解释

大家可能有的不明白啥意思啊,这个绿指的是就是 GitHub 的 Contribution,如果你每天都提交代码到 GitHub,至少一次 commit,那么 GitHub 就会在你当天对应的 Contribution 格子上点上绿色,比如我的就是这样子:

因为我经常在 GitHub 上提交代码,所以我的 Contribution 页面就显示为绿色,commit 多的就深,少的就浅,灰色的就是当然没有任何代码提交的。

所以这个问题问的就是,每天都在 GitHub 上提交代码是什么体验,也就是 365 天每天一天不落地撸代码是什么体验?

然后有一个回答看得我笑出声:

曾经保持了200多天全绿,但是冷落了女朋友,一直绿到现在。

原回答如下:

这哥们真的是太秀了,秀到我实在忍不住给他默默点了个赞…

不过咱们还是言归正传啊,说回这个问题。

其实说实话真的保持 365 天全绿真的是一件很难的事情,每周都会有周末吧,周末得陪女朋友吧?什么,你没有女朋友,那忽略这一条。

那即使没有女朋友,一年不得有几天是过年过节的,还撸啥代码啊?即使不是逢年过节,那也总有几天状态不好或者生病的吧,强如铁人那坚持 365 天天撸代码也是够神的。

不过我还真见过几个,实打实的大神,比如 Taylor Otweel,PHP Laravel 框架的开发者,一年 8000 多次 commit,他的 Contribution 是这样的:

这个是真的强,而且人家撸的代码质量肯定也高啊,不像我们可能改了点 README 啥的。

但强如 Taylor Otweel,你也能看到有些天是没有贡献的,毕竟人家周末可能就真的不撸代码或者有其他的安排。

那怎么才能做到 365 天全绿呢?

既然人不行,那就靠机器人吧。

GitHub 不是这两年出了个 GitHub Actions 的功能吗?这就是 CI 嘛,借助于它,我们想做到 GitHub 365 天全绿,就轻而易举了。

到底怎么做?难不难。

不难,可能只需要两分钟就搞定了。

想不想知道怎么做的?想知道的接着往下看。

方案

这里介绍一个 GitHub 的库,地址为:https://github.com/justjavac/auto-green,借助它,咔咔几步就能搞定了。

先说步骤。

首先打开这个仓库,点 Use this template,注意千万不要点 Fork,不然是不生效的,如图所示:

点了之后就是提示你用这个 Template 创建一个自己的 Git 残酷,这时候就会让你填写的的仓库名称,比如我就创建了一个名字叫做 AutoGreen 的仓库,如图所示:

至此,已经成功了一半了。

现在你会观察到,在 Actions 这个选项卡自动执行了一个任务。啪!很快啊,执行完了:

执行完了你会发现这个仓库多了一次 commit,是来自 justjavac 的一次 commit:

点进去看看,commit 的信息还叫 “a commit a day keeps your girlfriend away”,啥意思?

跟代码做好朋友,永远没有女朋友???Oh,No,不是这样的!

commit 如图所示:

神奇的是这次 commit 还没有任何 file change,还是个空的 commit。

行了,不管这么多了。

到现在就结束了吗?GitHub 以后就会天天绿了吗,当然不会绿啊,为啥啊?

扒一扒源码看一下,打开 .github/workflows/ci.yml 文件,结果如下:

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
name: ci

on:
push:
branches:
- master
schedule:
- cron: "0 0 * * *"

jobs:
autogreen:
runs-on: ubuntu-latest
steps:
- name: Clone repository
uses: actions/checkout@v2

- name: Auto green
run: |
git config --local user.email "justjavac@gmail.com"
git config --local user.name "迷渡"
git remote set-url origin https://${{ github.actor }}:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}
git pull --rebase
git commit --allow-empty -m "a commit a day keeps your girlfriend away"
git push

看看这个文件,意思就是每天执行一下下面的这几行代码,比如 git 的 config、commit、push 等,就相当于模拟了一次提交。

其中有几行代码比较有意思:

1
git remote set-url origin https://${{ github.actor }}:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}

这里就是指定远程仓库等地址,这里有几个占位符,github 的 actor 和 repository 对象,其实这些我们不用管,在运行的时候会被自动赋值为当前仓库的信息,另外还有 GITHUB_TOKEN 也是在该任务运行的时候自动添加的。

另外还有一行代码:

1
git commit --allow-empty -m "a commit a day keeps your girlfriend away"

这里的 commit 操作加上了一个 --allow-empty 选项,意思就是允许空的提交,这也就解释了上文空提交的缘由了。

OK,但这里我们再回过头来看看配置就知道为啥不算我们的提交了:

因为这里配置的是原作者的 GitHub 邮箱,所以这次提交当然就会算作原作者的了。

那怎么才能让我们的绿呢?那把邮箱改成我们自己的就好了,比如我的就修改为了这样子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
name: ci

on:
push:
branches:
- master
schedule:
- cron: "0 0 * * *"

jobs:
autogreen:
runs-on: ubuntu-latest
steps:
- name: Clone repository
uses: actions/checkout@v2

- name: Auto green
run: |
git config --local user.email "cqc@cuiqingcai.com"
git config --local user.name "Germey"
git remote set-url origin https://${{ github.actor }}:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}
git pull --rebase
git commit --allow-empty -m "a commit a day keeps your girlfriend away"
git push

直接在 GitHub 网页上点击修改,修改好了保存就行了。

修改完了之后我们就自动有了一次 commit,接着 Actions 还会自动触发一次 commit,最后结果如图所示:

可以看到,最后这次 commit 已经变成我自己了。

以后,我每天都可以自动绿了。

(这话怎么听着这么奇怪?

福利

最后,原作者还预留了一个定时任务,可以使得你想绿就绿,不仅可以让你每天都绿,还能让你每小时都绿,每分钟都能绿。

想绿就绿,其乐无穷。

绿的方式很简单,套用原作者的介绍了,修改 yml 文件的定时配置就好了:

计划任务语法有 5 个字段,中间用空格分隔,每个字段代表一个时间单位。

1
2
3
4
5
6
7
8
9
┌───────────── 分钟 (0 - 59)
│ ┌───────────── 小时 (0 - 23)
│ │ ┌───────────── 日 (1 - 31)
│ │ │ ┌───────────── 月 (1 - 12 或 JAN-DEC)
│ │ │ │ ┌───────────── 星期 (0 - 6 或 SUN-SAT)
│ │ │ │ │
│ │ │ │ │
│ │ │ │ │
* * * * *

每个时间字段的含义:

符号 描述 举例
* 任意值 * * * * * 每天每小时每分钟
, 值分隔符 1,3,4,7 * * * * 每小时的 1 3 4 7 分钟
- 范围 1-6 * * * * 每小时的 1-6 分钟
/ */15 * * * * 每隔 15 分钟

比如修改为 0 * * * * ,你就能做到每小时都绿了,修改为 * * * * * 你就能每分钟都绿了,真舒服啊!

:由于 GitHub Actions 的限制,如果设置为 * * * * * 实际的执行频率为每 5 分执行一次,也就是每 5 分钟绿一次,也还不错嘛~

技术杂谈

大家想必听说过一些变量命名格式,比如大驼峰式、小驼峰式,其他的你还知道什么?

今天看文档的时候,提到了一个 kebab case,这个你知道是什么命名格式吗?不知道的接着往下看。

这篇文章我们把所有变量命名格式梳理下。

Lower Camel Case

又名:小驼峰式

特征:第一个单词首字母小写,后续单词首字母大写。

例如:firstName、lastName

Upper Camel Case

又名:大驼峰式、Pascal Case

特征:每个单词的首字母都大写。

例如:FirstName、LastName

Snake Case

特征:每个单词小写,单词之间用下划线连接。

例如:first_name、last_name

Kebab Case

又名:Spinal Case、Train Case、Lisp Case

特征:每个单词小写,单词之间用中划线连接。

例如:first-name、last-name

Studly Case

特征:混合大小写,对大写字母的使用没有语义语法意义。有时只有元音是大写的,其他时候大写和小写是交替的,但通常只是随机的。这个名字来自讽刺或讽刺意味,它是作者试图传达自己的冷静的一种尝试。它也可以用来模拟营销人员在命名计算机软件包时违反标准英语案例惯例的行为,即使在没有技术要求的情况下也是如此。

例如:tHeqUicKBrOWnFoXJUmpsoVeRThElAzydOG

参考来源

  • https://en.wikipedia.org/wiki/Letter_case