0%

技术杂谈

就昨天的时候,Python 之父 Guido 发了个推特,说自己觉得退休太无聊了,然后于是乎决定加入微软。

同时推特上大神 Anders Hejlsberg(Delphi、C# 和 TypeScript 之父)转发了推特,表示希望可以一起工作。

要知道,去年那会去年 10 月 30 日,Python 之父 Guido 宣布退休,当时他发推文说,“这件事感觉既苦涩又甜蜜:苦涩的是,我马上要离开 Dropbox,现在已经退休;甜蜜的是,在 Dropbox 做工程师期间,我学到很多,比如类型标注就来自这段经历。我会非常怀念在这里工作的日子。”

看到这个消息我既震惊又激动,Python 之父加入我司了?那我岂不是和他也算做同事了?哈哈哈。

当时我看到这个消息之后,第一时间就去公司的系统里面查了下,果真就查到了,这里放个图。

他的 Title 叫 DISTINUISHED ENGINEER,这个 Title 可不是一般的牛逼,在微软,有这样的 Title 的人可是屈指可数的,DISTINUISHED 意思就是杰出的,非常牛逼的意思,能有这种 Title 的是为业界或公司做出过特别特别大贡献的,特别有影响力的,可能普通人在微软呆个二十多年都不一定能到这个地位。他的职级和 Report Line 就不说了,他距离微软 CEO 纳德拉只差 3 级,稍微形象点说就是对标阿里 P11 或者 P12 的位置吧。

他的部门就直接写了 Python and Tools for AI,和 AI 相关,同时 Guido 又这么热爱开源,微软也在一直拥抱开源,期待 Python 之父将来不久之后又出新的杰作吧。

要知道 Python 之父 Guido 已经 64 岁了,或许大佬就是大佬吧,退休了感觉无聊,就来微软来玩玩。怎么说呢?他这个地位就是类似达成一些自我追求和实现自己的一些价值,顺便解解闷,这种境界完全不是我们能比的了。反过来想想中国这个环境,多少人都是用代码来换取一份工作谋求生存,然后 35 岁干不动了可能就被辞退。本来想展开说说的,但是感觉说来都是痛,就不多说了,希望我将来不会为生活所迫,能够自由地继续追求自己热爱的东西。

另外本想说说 Guido 的履历的,但看到其他的一些文章已经整理得挺好了,我就不再重复了,大家想了解更多的话可以去看看 https://mp.weixin.qq.com/s/ZQJClbYiKP5cAnKjB_ZfUg。

总之,我就是来宣布这个和 Python 之父成为“同事”的开心的消息,值得纪念下,然后顺便期待下 Python 之父将来出新的杰作!

技术杂谈

本章将告诉你该如何去对request模块进行二次封装,暂时并不会告诉你HTTP协议及原理、URL等相关。当然你会使用然后在来阅读此文章一定会另有所获。我已经迫不及待要告诉你这个小秘密,以及想与你交流了。没时间解释了,快来一起和我一起探讨相关的内容吧

官方文档对requests的定义为:Requests 唯一的一个非转基因的 Python HTTP 库,人类可以安全享用。

使用Python写做爬虫的小伙伴一定使用过requests这个模块,初入爬虫的小伙伴也一定写过N个重复的requests,这是你的疑问。当然也一直伴随着我,最近在想对requests如何进行封装一下,让他支持支持通用的函数。若需要使用,直接调用即可。

那么问题来了,如果要写个供自己使用通用的请求函数将会有几个问题

  • requests的请求方式(GET\POST\INPUT等等)
  • 智能识别网站的编码,避免出现乱码
  • 支持文本、二进制(图片、视频等为二进制内容)
  • 以及还需要傻瓜一点,那就是网站的Ua(Ua:User-Agent,基本上网站都会验证接受到请求的Ua。来初步判断是爬虫还是用户)

那么咱们就针对以上问题开干吧

Requests的安装

在确保python环境搭建完成后直接使用pip或者conda命令进行安装,安装命令如下:

1
2
3
4
5
pip install requests
conda install requests

# 或者下载过慢点话,可以使用国内的pip镜像源,例如:
pip install requests -i https://pypi.tuna.tsinghua.edu.cn/simple/

安装完成后,效果图如下:

初探requests基本使用

HTTP 中最常见的请求之一就是 GET 请求,下面我们来详细了解利用 requests 库构建 GET 请求的方法。

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
import requests

response = requests.get('http://httpbin.org/get')

# 响应状态码
print("response.status_code:", response.status_code)
# 响应头
print("response.headers:", response.headers)
# 响应请求头
print("response.request.headers:", response.request.headers)
# 响应二进制内容
print("response.content:", response.content)
# 响应文本
print("response.text", response.text)

# 返回如下
response.status_code: 200
response.headers: {'Date': 'Thu, 12 Nov 2020 13:38:05 GMT', 'Content-Type': 'application/json', 'Content-Length': '306', 'Connection': 'keep-alive', 'Server': 'gunicorn/19.9.0', 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Credentials': 'true'}
response.request.headers: {'User-Agent': 'python-requests/2.24.0', 'Accept-Encoding': 'gzip, deflate', 'Accept': '*/*', 'Connection': 'keep-alive'}
response.content: b'{\n "args": {}, \n "headers": {\n "Accept": "*/*", \n "Accept-Encoding": "gzip, deflate", \n "Host": "httpbin.org", \n "User-Agent": "python-requests/2.24.0", \n "X-Amzn-Trace-Id": "Root=1-5fad3abd-7516d60b3e951824687a50d8"\n }, \n "origin": "116.162.2.166", \n "url": "http://httpbin.org/get"\n}\n'
{
"args": {},
"headers": {
"Accept": "*/*",
"Accept-Encoding": "gzip, deflate",
"Host": "httpbin.org",
"User-Agent": "python-requests/2.24.0",
"X-Amzn-Trace-Id": "Root=1-5fad3abd-7516d60b3e951824687a50d8"
},
"origin": "116.162.2.166",
"url": "http://httpbin.org/get"
}

requests基本使用已经经过简单的测试了,是否有一点点feel呢?接下来我们直接将它封装为一个函数以供随时调用

示例如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import requests

urls = 'http://httpbin.org/get'


def downloader(url, headers=None):
response = requests.get(url, headers=headers)
return response


print("downloader.status_code:", downloader(url=urls).status_code)
print("downloader.headers:", downloader(url=urls).headers)
print("downloader.request.headers:", downloader(url=urls).request.headers)
print("downloader.content:", downloader(url=urls).content)
print("downloader.text", downloader(url=urls).text)

# 返回效果如上所示,此处省略

以上我们就把,请求方法封装成了一个函数。将基本的url,headers以形参的方式暴露出来,我们只需传入需要请求的url即可发起请求,至此一个简单可复用的请求方法咱们就完成啦。

完~~~

以上照顾新手的就基本完成了,接下来我们搞点真家伙。

二次封装

请求函数的封装

由于请求方式并不一定(有可能是GET也有可能是POST),所以我们并不能智能的确定它是什么方式发送请求的。

Requests中request方法以及帮我们实现了这个方法。我们将他的请求方式暴露出来,写法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
urls = 'http://httpbin.org/get'


def downloader(url, method=None, headers=None):
_method = "GET" if not method else method
response = requests.request(url, method=_method, headers=headers)
return response


print("downloader.status_code:", downloader(url=urls).status_code)
print("downloader.headers:", downloader(url=urls).headers)
print("downloader.request.headers:", downloader(url=urls).request.headers)
print("downloader.content:", downloader(url=urls).content)
print("downloader.text", downloader(url=urls).text)

由于大部分都是GET方法,所以我们定义了一个默认的请求方式。如果需要修改请求方式,只需在调用时传入相对应的方法即可。例如我们可以这样

1
downloader(urls, method="POST")

文本编码问题

解决由于request的误差判断而造成解码错误,而得到乱码。

此误差造成的原因是可能是响应头的Accept-Encoding,另一个是识别错误

1
2
# 查看响应编码
response.encoding

此时我们需要借用Python中C语言编写的cchardet这个包来识别响应文本的编码。安装它

1
2
pip install cchardet -i  https://pypi.tuna.tsinghua.edu.cn/simple/
# 如果pip直接安装失败的话,直接用清华源的镜像。
1
2
3
4
5
6
7
8
9
10
# 实现智能版的解码:如下
encoding = cchardet.detect(response.content)['encoding']



def downloader(url, method=None, headers=None):
_method = "GET" if not method else method
response = requests.request(url, method=_method, headers=headers)
encoding = cchardet.detect(response.content)['encoding']
return response.content.decode(encoding)

区分二进制与文本的解析

在下载图片、视频等需获取到其二进制内容。而下载网页文本需要进行encode。

同理,我们只需要将一个标志传进去,从而达到分辨的的效果。例如这样

1
2
3
4
5
def downloader(url, method=None, headers=None, binary=False):
_method = "GET" if not method else method
response = requests.request(url, method=_method, headers=headers)
encoding = cchardet.detect(response.content)['encoding']
return response.content if binary else response.content.decode(encoding)

默认Ua

在很多时候,我们拿ua又是复制。又是加引号构建key-value格式。这样有时候仅仅用requests做个测试。就搞的麻烦的很。而且请求过多了,直接就被封IP了。没有自己的ip代理,没有钱又时候还真有点感觉玩不起爬虫。

为了减少被封禁IP的概率什么的,我们添加个自己的Ua池。Ua池的原理很简单,内部就是采用随机的Ua,从而减少被发现的概率.至于为什么可以达到这这样的效果,在这里仅作简单介绍。详细可能要从计算机网络原理说起。

结论就是你一个公司里大多采用的都是同一个外网ip去访问目标网址。那么就意味着可能你们公司有N个人使用同一个ip去访问目标网址。而封禁做区分的一般由ip访问频率和浏览器的指纹和在一起的什么鬼东东。简单理解为Ua+ip访问频率达到峰值,你IP就对方关小黑屋了。

构建自己的ua池,去添加默认的请求头,

Ua有很多,这里就不放出来了,如果有兴趣可以直接去源码里面拿。直接说原理:构造很多个Ua,然后随机取用。从而降低这个同一访问频率:同时也暴露端口方便你自己传入header

1
2
3
4
5
6
7
8
9
from powerspider.tools.Ua import ua
import requests

def downloader(url, method=None, header=None, binary=False):
_headers = header if header else {'User-Agent': ua()}
_method = "GET" if not method else method
response = requests.request(url, method=_method, headers=_headers)
encoding = cchardet.detect(response.content)['encoding']
return response.content if binary else response.content.decode(encoding)

那么基本的文件都已经解决了,不过还不完美。异常处理,错误重试,日志什么都没。这怎么行呢。活既然干了,那就干的漂漂亮亮的。

来让我们加入进来这些东西

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
import cchardet
from retrying import retry
from powerspider import logger
from powerspider.tools.Ua import ua
from requests import request, RequestException


@retry(stop_max_attempt_number=3, retry_on_result=lambda x: x is None, wait_fixed=2000)
def downloader(url, method=None, header=None, timeout=None, binary=False, **kwargs):
logger.info(f'Scraping {url}')
_header = {'User-Agent': ua()}
_maxTimeout = timeout if timeout else 5
_headers = header if header else _header
_method = "GET" if not method else method
try:
response = request(method=_method, url=url, headers=_headers, **kwargs)
encoding = cchardet.detect(response.content)['encoding']
if response.status_code == 200:
return response.content if binary else response.content.decode(encoding)
elif 200 < response.status_code < 400:
logger.info(f"Redirect_URL: {response.url}")
logger.error('Get invalid status code %s while scraping %s', response.status_code, url)
except RequestException as e:
logger.error(f'Error occurred while scraping {url}, Msg: {e}', exc_info=True)


if __name__ == '__main__':
print(downloader("https://www.baidu.com/", "GET"))

至此,我们的对Requests二次封装,构造通用的请求函数就已经完成了。

源码地址:https://github.com/PowerSpider/PowerSpider/tree/dev

期待下次再见

技术杂谈

前阵子有个朋友问我一个 Python 相关的问题,然后我把解决方案简单用代码写了一下发给他,结果他说跑不起来,我就很纳闷,确认了下他说 Python 库也都装了,但是运行的时候还是会报库找不到。果不其然,经过一段时间沟通发现,它的机器上装了两个 Python 环境,装 Python 库的时候是用的 pip 安装的,它对应了一个 Python 环境,但是运行的时候却是用的使用的 python3 命令却是对应另外一个 Python 环境,理所当然就跑不通了。这个问题其实挺常见的,有时候跟对方说检查下 Python 环境路径什么的,但是对方有时候并不知道怎么做,或者直接得再告诉他用 python3 -m pip install ... 命令来装包。

另外我还有个需求是,我写了一些代码,想直接把运行结果和代码发给别人看,一种方法可以直接借助于 GitHub,代码放上去,然后让对方 Clone 下来再运行。对方也得配置下相关的环境才能正确跑起来。另一种方法则是打包 Docker 镜像,但是这也得额外花一些时间。有没有更好的方案解决这个问题呢?

上面这就是两个问题:

  • 环境不一致的问题
  • 环境共享的问题

这俩问题有通用的解决方案吗?有!答案就是云 IDE。有了云 IDE,不仅以上两个问题可以解决,甚至都不用自己配置开发环境了,我们只需要一个浏览器就能在线编辑代码,同时这个 IDE 还能分享给他人,他人打开之后,看到的就是和我一模一样的内容,甚至还可以看到我正在编写代码的实时效果。

美哉美哉!

今天就给大家介绍一款云 IDE,是华为家的,叫做「华为云CloudIDE」,我试用了一下感觉还不错,它的官方介绍页面是这样的:

image-20201111005857785

官方介绍如下:

通过华为云CloudIDE,您仅需一个浏览器就可以编辑、调试、运行您的代码。

开发环境通常包括各种配置文件、特定的语言、框架版本、以及其他个人设备等等一系列个人配置。当您开发一个新的项目或者测试一些程序时,不得不对已有的个人配置做相应的调整。或者,您可以通过CloudIDE将个人的偏好设置转移到云端。CloudIDE将存储项目设置,实现您与他人共享,并通过浏览器授予访问的权限。您可以将所有文件保存在云环境中,并通过各种访问设备操作管理它们。开发环境在云端,您可以实现代码阅读、编写、调试、运行,也可以将代码存入代码仓库,随时、随地、随心!

和我刚才说的就是一样的,利用它我们可以解决诸多问题:如环境配置、环境统一、环境共享的问题,同时我们还可以随时随地编辑代码,丝毫不受机器的限制,上云之后,一切都是那么方便。

好,这里我就带大家来体验下「华为云CloudIDE」吧。

首先,目前「华为云CloudIDE」是可以免费体验的,每个人每天有 120 分钟的免费体验时间,如果要获得更好的体验可以创建付费实例。

目前免费体验需要有这些规定:

尊敬的用户,您已经开通CloudIDE服务,免费体验需要遵守如下约定:

  • 每天的免费体验时间为120分钟,少于5分钟不能创建新的体验;
  • 每个实例可使用时长为60分钟,60分钟后,实例将会被删除,并且数据将会被删除;
  • 同一时间只能体验一个实例,打开第二个实例将会自动关闭第一个实例;
  • 免费体验不能保证最佳使用体验,您可能需要排队等候,付费使用可以获得更好的体验

完全没问题,体验完全足够了。

我这里操作的 URL 是 https://devcloud.cn-north-4.huaweicloud.com/cloudide/trial,大家可以注册个华为云账号就能看到对应的入口页面了,就是上文发的图。

由于我对 Python 比较熟悉,那么我就来体验一个 Python 环境吧,找到下方的 Python 环境,然后点击「免费体验」。

这时候环境就会变成「启动中」的状态,如图所示:

image-20201111010644754

然后接下来我们就会进入到云 IDE 的环境,等待一顿初始化之后之后,我们就可以看到如下页面,如图所示:

image-20201111010740454

嗯?有没有熟悉的感觉?

没错,这和 VS Code 可太像了,简直就是一个在线版的 VS Code。不过仔细看看,其实还是有些不一样的,比如左上角的 Logo,插件仓库以及一些菜单选项。可以说是 VS Code 的魔改版。

我们看下左侧栏其他的菜单,搜索、版本控制、调试没啥好说的,看看插件仓库吧,打开之后是这样的:

image-20201111011151570

这里我们可以看到「华为云CloudIDE」已经为我们预装了一些插件,如 Python。另外下方还有一些推荐的插件,例如 Huawei Cloud Toolkit,这些插件的安装方式和 VS Code 是一模一样的。

接下来我们再看看菜单的一些配置,随便挑几个看看吧:

image-20201111011434518

image-20201111011453092

几乎所有功能菜单和 VS Code 是一样的,不过「华为云CloudIDE」添加了一些入口,比如 Close Instance 等等,点了之后就会关闭当前云 IDE 实例。

OK,接下来我们再回到代码看看,看看它给初始化了个什么,代码结构是这样的:

image-20201111011717465

就是一个基本的 Flask 的 Hello World。

那来跑跑试试吧,点击右上角的运行按钮,如图所示:

image-20201111011824402

哦,居然报错了,内容如下:

image-20201111011856800

这里报错说找不到 flask 这个包,那很明显就是没安装了,正好我们可以看到 requirements.txt 定义了相关的依赖环境,那我们来手动安装下吧。

在当前命令行下输入:

1
pip3 install -r requirements.txt

运行结果类似如下:

image-20201111012030928

看起来不错,另外可以观察到它是从 https://repo.huaweicloud.com/repository/pypi/ 这里下载的 Python 包,速度也挺快的。简直是意外收获呢,又发现了一个不错的 Pypi 加速镜像。

顺便看看 pip.conf 配置吧:

1
cat ~/.pip/pip.conf

结果如下:

1
2
3
4
5
[global]
index-url = https://repo.huaweicloud.com/repository/pypi/simple
trusted-host = repo.huaweicloud.com
timeout = 120
extra-index-url = https://obs-workspace-20180810.obs.cn-north-1.myhuaweicloud.com/pypi/simple

一目了然,可以看到 pip 全局配置了一个加速镜像,index-url 配置为了 https://repo.huaweicloud.com/repository/pypi/simple,这个是可以公开访问的,所以以后

我们装包的时候就可以这么来装了:

1
pip3 install requests -i https://repo.huaweicloud.com/repository/pypi/simple

速度还是挺不错的,美滋滋,意外收获。

OK,切回正题,这时候我们已经装完环境了,重新运行 app.py,这时候就可以发现运行成功了,结果类似如下:

image-20201111012729299

可以看到它就在 8080 端口上运行了一个服务,那咋访问呢?

在运行的同时,还弹出了这么一个窗口,如图所示:

image-20201111012846946

似乎是提示我们可以注册一个外部端口将服务暴露出去,我们点击 Register 和 OK。

接下来 OK 按钮就变成了 Access 按钮,如图所示:

image-20201111013057453

还带了一个二维码和 Copy 按钮,我们 Copy 一下看看,我 Copy 之后的 URL 是 https://sermgcaktl-8080-cce-5.lf.templink.dev/,然后我在浏览器中打开,显示如下:

image-20201111013157227

很不错啊,这是自动给我们生成了一个域名,然后还把服务暴露出去可供访问了,挺好。

接下来我们再看看 Workspace 吧,切换下代码文件夹,点击 File - Open ,可以看到如下页面:

image-20201111013747428

可以看到当前打开的是 python3-sample 这个文件夹,那能换其他文件夹吗?我在上方输入个根目录 / 看看,结果如图所示:

image-20201111013852795

出现了熟悉的 Linux 文件树结构,那可以断定这其实就是一个 Linux 环境了,那岂不是干啥事都行了。

找到 /home/user/projects 目录,看起来是用来专门存放项目的目录。

另外我再试试能否随便打开一个 GitHub 上的项目吧,比如在控制台进入刚才这个目录,然后 Clone 一个项目,命令如下:

1
2
cd /home/user/projects/
git clone https://github.com/Python3WebSpider/ScrapyTutorial.git

OK,简单 Clone 了一个 Scrapy 项目,成功!如图所示:

image-20201111014223373

然后打开下这个项目,如图所示:

image-20201111015026872

打开 Terminal,安装下依赖:

1
pip3 install scrapy pymongo

另外这个项目依赖于 MongoDB,不过我们这里仅仅测试,可以取消它的依赖,setting.py 中注释掉如下代码:

1
2
3
4
5
ITEM_PIPELINES = {
'tutorial.pipelines.TextPipeline': 300,
- 'tutorial.pipelines.MongoDBPipeline': 400,
+ #'tutorial.pipelines.MongoDBPipeline': 400,
}

然后接下来可以运行如下命令启动爬虫了:

1
scrapy crawl quotes

这时候就可以看到爬虫就成功运行了,如图所示:

image-20201111015327574

完美,Clone 的项目也成功运行了,美滋滋。

最后我们看看这个退出之后怎么样,先复制下当前的 URL,我的 URL 是 https://ide.devcloud.cn-north-4.huaweicloud.com/cce-5/sermgcaktl/#/home/user/projects/ScrapyTutorial,然后我们直接关掉当前页面选项卡,然后重新进入,看看现在的运行状态还在不在。

牛逼!重新进入之后还是原来的样子,控制台依然还是开的,跑的结果也依然有!

image-20201111015741812

很好!

那这个环境怎么分享给别人呢?看了下「华为云CloudIDE」是支持分享功能的,这里可以进入到 CloudIDE 的首页,点击管理,然后通过白名单或者黑名单的方式添加用户就可以了。

image-20201111020023870

好了,到现在为止「华为云CloudIDE」就体验完了,感觉整体上还是挺不错的,大家也可以来体验试试看吧!

技术杂谈

YAML文件概述

K8s集群文件中对资源管理和资源对象编排部署都可以通过声明样式yaml,文件来解决,也就是说可以把需要对资源对象操作编辑到yaml,文件中。

我们称之为资源清单资源清单文件通过kubectl命令直接使用资源清单文件就可以实现对大量资源对象进行编排部署

基本语法

  • 大小写敏感
  • 使用缩进表示层级关系,缩进不允许使用tab,只允许空格
  • 缩进的空格数不重要,只要相同层级的元素左对齐即可
  • ‘#’表示注释
  • ---表示新的yaml文件的开始

    数据类型

    YAML 支持以下几种数据类型:
  • 对象:键值对的集合,又称为映射(mapping)/ 哈希(hashes) / 字典(dictionary)
  • 数组:一组按次序排列的值,又称为序列(sequence) / 列表(list)
  • 纯量(scalars):单个的、不可再分的值

    常量

    常量是最基本的,不可再分的值,包括:
  • 字符串
  • 布尔值
  • 整数
  • 浮点数
  • Null
  • 时间
  • 日期

    引用

    & 锚点和 * 别名,可以用来引用:

    & 用来建立锚点(defaults),<< 表示合并到当前数据,* 用来引用锚点。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    defaults: &defaults
    adapter: postgres
    host: localhost
    development:
    database: myapp_development
    <<: *defaults
    test:
    database: myapp_test
    <<: *defaults
    ---相当于
    defaults:
    adapter: postgres
    host: localhost
    development:
    database: myapp_development
    adapter: postgres
    host: localhost
    test:
    database: myapp_test
    adapter: postgres
    host: localhost

    kubernetes中yaml组成部分

    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
    apiVersion: v1								# API版本 可使用命令kubeclt api--verison查看
    kind: ReplicationController # 资源类型 - 副本控制器RC
    metadata: # 资源元数据
    name: mysql # - RC的名称(全局唯一),符合目标的Pod拥有此标签
    spec: # 资源的规格(RC的相关属性的定义)
    replicas: 1 # 副本的期望数量
    selector: # 标签选择器
    app: mysql
    # *********************************************
    template: # Pod 模版
    metadata:
    name: mysql
    labels: # 标签 Pod 副本拥有的标签,对应RC的Selector
    app: mysql
    spec: # Pod规格
    containers: # 容器的配置
    - name: mysql # 容器名称
    image: mysql # 容器镜像(对应的Docker images)
    ports:
    - containerPort: 3306 # 容器引用监听的端口号
    env: # 环境配置
    - name: MYSQL_ROOT_PASSWORD
    value: "123456"


    ---
    apiVersion: v1
    kind: Service # 资源类型 服务
    metadata:
    name: mysql
    spec:
    selector:
    app: mysql
    ports:
    - port: 3306
  • 控制器部分
    控制器部分
  • 被控制的对象
    被控制的对象
  • RC
    RC

    快速编写yaml文件

    Part 1:使用命令生成yaml文件

    1
    2
    3
    4
    5
    6
    # kubectl create 
    kubectl create deployment web --image=nginx -o yaml --dry-run
    # -o 使用yaml格式展示
    # -dry-run 尝试运行,并不会真正运行
    # 保存至myweb.yaml
    kubectl create deployment web --image=nginx -o yaml --dry-run > myweb.yaml
  • kubectl create deployment web —image=nginx -o yaml —dry-run运行效果如下⬇️:
    kubectl create

    Part 2: 使用命令导出yaml文件

    1
    2
    3
    kubectl get
    kubectl get deploy # 查看部署
    kubectl get deploy nginx -o yaml --export > myweb.yaml

kubectl命令学习

这里简单记录下 Kubectl 部署的一些准备工作。

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
Basic Commands (Beginner):
create # Create a resource from a file or from stdin.
expose # 使用 replication controller, service, deployment 或者 pod 并暴露它作为一个 新的
Kubernetes Service
run # 在集群中运行一个指定的镜像
set # 为 objects 设置一个指定的特征

Basic Commands (Intermediate):
explain # 查看资源的文档
get # 显示一个或更多 resources
edit # 在服务器上编辑一个资源
delete # Delete resources by filenames, stdin, resources and names, or by resources and label selector

Deploy Commands:
rollout # Manage the rollout of a resource
scale # 为 Deployment, ReplicaSet, Replication Controller 或者 Job 设置一个新的副本数量
autoscale # 自动调整一个 Deployment, ReplicaSet, 或者 ReplicationController 的副本数量

Cluster Management Commands:
certificate # 修改 certificate 资源.
cluster-info # 显示集群信息
top # Display Resource (CPU/Memory/Storage) usage.
cordon # 标记 node 为 unschedulable
uncordon # 标记 node 为 schedulable
drain # Drain node in preparation for maintenance
taint # 更新一个或者多个 node 上的 taints

Troubleshooting and Debugging Commands:
describe # 显示一个指定 resource 或者 group 的 resources 详情
logs # 输出容器在 pod 中的日志
attach # Attach 到一个运行中的 container
exec # 在一个 container 中执行一个命令
port-forward # Forward one or more local ports to a pod
proxy # 运行一个 proxy 到 Kubernetes API server
cp # 复制 files 和 directories 到 containers 和从容器中复制 files 和 directories.
auth # Inspect authorization

Advanced Commands:
diff # Diff live version against would-be applied version
apply # 通过文件名或标准输入流(stdin)对资源进行配置
patch # 使用 strategic merge patch 更新一个资源的 field(s)
replace # 通过 filename 或者 stdin替换一个资源
wait # Experimental: Wait for a specific condition on one or many resources.
convert # 在不同的 API versions 转换配置文件
kustomize # Build a kustomization target from a directory or a remote url.

Settings Commands:
label # 更新在这个资源上的 labels
annotate # 更新一个资源的注解
completion # Output shell completion code for the specified shell (bash or zsh)

Other Commands:
api-resources # Print the supported API resources on the server
api-versions # Print the supported API versions on the server, in the form of "group/version"
config # 修改 kubeconfig 文件
plugin # Provides utilities for interacting with plugins.
version # 输出 client 和 server 的版本信息

Kubectl create

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
Create a resource from a file or from stdin.

JSON and YAML formats are accepted.

Examples:
# Create a pod using the data in pod.json.
kubectl create -f ./pod.json

# Create a pod based on the JSON passed into stdin.
cat pod.json | kubectl create -f -

# Edit the data in docker-registry.yaml in JSON then create the resource using the edited data.
kubectl create -f docker-registry.yaml --edit -o json

Available Commands:
clusterrole # Create a ClusterRole.
clusterrolebinding # 为一个指定的 ClusterRole 创建一个 ClusterRoleBinding
configmap # 从本地 file, directory 或者 literal value 创建一个 configmap
cronjob # Create a cronjob with the specified name.
deployment # 创建一个指定名称的 deployment.
job # Create a job with the specified name.
namespace # 创建一个指定名称的 namespace
poddisruptionbudget # 创建一个指定名称的 pod disruption budget.
priorityclass # Create a priorityclass with the specified name.
quota # 创建一个指定名称的 quota.
role # Create a role with single rule.
rolebinding # 为一个指定的 Role 或者 ClusterRole创建一个 RoleBinding
secret # 使用指定的 subcommand 创建一个 secret
service # 使用指定的 subcommand 创建一个 service.
serviceaccount # 创建一个指定名称的 service account

Options:
--allow-missing-template-keys=true: If true, ignore any errors in templates when a field or map key is missing in the template. Only applies to golang and jsonpath output formats.
--dry-run=false: If true, only print the object that would be sent, without sending it.
--edit=false: Edit the API resource before creating
-f, --filename=[]: Filename, directory, or URL to files to use to create the resource
-k, --kustomize='': Process the kustomization directory. This flag can't be used together with -f or -R.
-o, --output='': Output format. One of:
json|yaml|name|go-template|go-template-file|template|templatefile|jsonpath|jsonpath-file.
--raw='': Raw URI to POST to the server. Uses the transport specified by the kubeconfig file.
--record=false: Record current kubectl command in the resource annotation. If set to false, do not record the
command. If set to true, record the command. If not set, default to updating the existing annotation value only if one
already exists.
-R, --recursive=false: Process the directory used in -f, --filename recursively. Useful when you want to manage
related manifests organized within the same directory.
--save-config=false: If true, the configuration of current object will be saved in its annotation. Otherwise, the
annotation will be unchanged. This flag is useful when you want to perform kubectl apply on this object in the future.
-l, --selector='': Selector (label query) to filter on, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2)
--template='': Template string or path to template file to use when -o=go-template, -o=go-template-file. The
template format is golang templates [http://golang.org/pkg/text/template/#pkg-overview].
--validate=true: If true, use a schema to validate the input before sending it
--windows-line-endings=false: Only relevant if --edit=true. Defaults to the line ending native to your platform.

Usage:
kubectl create -f FILENAME [options]

技术杂谈

最近博客换上了 Hexo,但是发现经常莫名其妙构建失败,报内存溢出,Out of Memory,错误如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
==== JS stack trace =========================================

0: ExitFrame [pc: 0x1409219]
Security context: 0x36ffd21408d1 <JSObject>
1: get sticky [0x36ffd2149801](this=0x1e69c6f730e1 <JSRegExp <String[10]: [\t \n\r]+>>)
2: match [0x6ca6396ae39] [/github/workspace/node_modules/js-beautify/js/src/core/inputscanner.js:~110] [pc=0x1d19a16be4dc](this=0x1e69c6f73119 <InputScanner map = 0x1c206a47a549>,0x1e69c6f730e1 <JSRegExp <String[10]: [\t \n\r]+>>)
3: tokenize [0x6ca6396e171] [/gith...

1: 0xa17c40 node::Abort() [hexo]
2: 0xa1804c node::OnFatalError(char const*, char const*) [hexo]
3: 0xb95a7e v8::Utils::ReportOOMFailure(v8::internal::Isolate*, char const*, bool) [hexo]
4: 0xb95df9 v8::internal::V8::FatalProcessOutOfMemory(v8::internal::Isolate*, char const*, bool) [hexo]
5: 0xd53075 [hexo]
6: 0xd53706 v8::internal::Heap::RecomputeLimits(v8::internal::GarbageCollector) [hexo]
7: 0xd5ffc5 v8::internal::Heap::PerformGarbageCollection(v8::internal::GarbageCollector, v8::GCCallbackFlags) [hexo]
8: 0xd60e75 v8::internal::Heap::CollectGarbage(v8::internal::AllocationSpace, v8::internal::GarbageCollectionReason, v8::GCCallbackFlags) [hexo]
9: 0xd6251f v8::internal::Heap::HandleGCRequest() [hexo]
10: 0xd10f85 v8::internal::StackGuard::HandleInterrupts() [hexo]
11: 0x106c5c6 v8::internal::Runtime_StackGuard(int, unsigned long*, v8::internal::Isolate*) [hexo]
12: 0x1409219 [hexo]

原因就是内存溢出了,这是因为 Hexo 在构建的时候存了一个特别大的数组,而默认 Node 运行时最大内存为 512 MB,详情可以见:https://github.com/hexojs/hexo/issues/2165。

真是服了,才几百篇文章就不行了,耗费内存至于这么多吗?

解决方案,可以直接设置 Node 的最大内存限制,比如我直接设置为 16G,在构建之前执行如下命令就行了:

1
export NODE_OPTIONS="--max-old-space-size=16384"

这样就设置了 Node 运行时的最大内存,就不会触发内存溢出了。

Markdown

在写文章的时候我经常会遇到这么一个需求,我想要跟大家说明某一行代码需要改动成另外一行代码。

比如我这里有一段代码:

1
2
3
4
5
6
FROM python:3.6
WORKDIR /app
COPY . .
RUN pip install -r requirements.txt
VOLUME ["/app/proxypool/crawlers/private"]
CMD ["supervisord", "-c", "supervisord.conf"]

仅仅作为示例,这里行号我也没标,行数也有可能很多行,比如上百行。

比如我就想告诉大家,我想把:

1
RUN pip install -r requirements.txt

改成:

1
RUN pip install -r requirements.txt -i https://pypi.douban.com/simple

这一行。

看,我已经说完了。

为了说这件事,我需要打好几行字,我得把原来的代码打上,然后说「改成」,然后再把新代码打上。

麻烦吧?

有没有什么更好的表述方式呢?

有。

看这里:

1
2
-RUN pip install -r requirements.txt
+RUN pip install -r requirements.txt -i https://pypi.douban.com/simple

是不是很直观?

红色代表删减,绿色代表增加。

经常 Code Review 的朋友一定倍感亲切。

那这个怎么实现的呢?其实这就是用了 Markdown 高亮的一种写法,只需要把语言改成 diff 就好了。

原语言如下:

这里为了防止 Markdown 解析冲突,我就用图片了。

反正就是语言标注改成 diff,然后需要删除的前面加个减号,需要增加的加个加号就行了。

完毕!

希望有帮助!

技术杂谈

kubernetes-install

操作系统初始化

  • 关闭防火墙(all)
1
2
3
4
5
6
# 临时关闭防火墙
systemctl stop firewalld
# 永久关闭防火墙
systemctl disable firewalld
# 验证
systemctl status firewalld
  • 关闭selinux(all)
1
2
3
4
# 临时关闭
setenforce 0
# 永久
sed -i 's/enforcing/disabled/' /etc/selinux/config
  • 关闭swap(all)
1
2
3
4
# 临时
swapoff -a
# 永久
sed -ri 's/.*swap.*/#&/' /etc/fstab
  • 设置主机名称(all)
1
2
3
4
# 设置名称(k8s-m-1)忽略大写字母
hostnamectl set-hostname k8s-m-1
# 验证
hostname
  • Master添加Hostname(master)
1
2
3
4
5
6
7
8
9
10
11
12
# 设置
cat >> /etc/hosts << EOF
masterIp master
node1Ip node1
node2Ip node2
EOF
# eg
cat >> /etc/hosts << EOF
192.168.50.212 k8s-m-1
192.168.50.87 k8s-n-1
192.168.50.85 k8s-n-2
EOF
  • 将桥接的IPV4 流量传递到iptables的链(all)
1
2
3
4
5
6
cat > /etc/sysctl.d/k8s.conf << EOF
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-ip6tables = 1
EOF
# 生效
sysctl --system
  • 时间同步(All)
1
2
3
yum install -y ntpdate 
ntpdate time.windows.com
# 三台机子输出如下则成功(相差几秒或几分为正常现象)

安装Docker

官方文档-安装

  • Docker安装sh Script:(All)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# You can use scripts for one click installation,You may need to type enter at the end
# remove docker
sudo yum remove docker \
docker-client \
docker-client-latest \
docker-common \
docker-latest \
docker-latest-logrotate \
docker-logrotate \
docker-engine
# Set up repository
sudo yum install -y yum-utils
# Use Aliyun Docker
sudo yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
# install docker from yum
yum install -y docker-ce docker-ce-cli containerd.io
# restart docker
systemctl restart docker
# cat version
docker --version

  • 配置加速(all)
j
1
2
3
4
5
6
7
8
9
10
11
12
sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json <<-'EOF'
{
"registry-mirrors": ["https://etdea28s.mirror.aliyuncs.com"]
}
EOF

# reload
sudo systemctl daemon-reload
sudo systemctl restart docker

# 检查阿里云加速

kubernetes安装

  • 配置kubernetes源(all)
1
2
3
4
5
6
7
8
9
cat <<EOF > /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64/
enabled=1
gpgcheck=1
repo_gpgcheck=1
gpgkey=https://mirrors.aliyun.com/kubernetes/yum/doc/yum-key.gpg https://mirrors.aliyun.com/kubernetes/yum/doc/rpm-package-key.gpg
EOF

由于官网kubernetes源在国外有墙,直接使用官方源会导致安装失败。所以我们配置国内的阿里源

  • 安装 kubectl kubelet kubeadm(all)
1
2
3
4
# install kubectl kubelet kubeadm
yum install -y kubectl kubelet kubeadm
# set boot on opening computer
systemctl enable kubelet
  • 初始化k8s部署(Master)
1
2
3
4
5
6
7
8
9
10
11
12
13
kubeadm init \
--apiserver-advertise-address=youselfIp of Master \
--image-repository registry.aliyuncs.com/google_containers \
# 不冲突即可
--service-cidr=10.10.0.0/16 \
--pod-network-cidr=10.122.0.0/16

# eg
kubeadm init \
--apiserver-advertise-address=192.168.50.212 \
--image-repository registry.aliyuncs.com/google_containers \
--service-cidr=10.10.0.0/16 \
--pod-network-cidr=10.122.0.0/16

常见错误:running with swap on is not supported. Please disable swap

[preflight] If you know what you are doing, you can make a check non-fatal with `—ignore-preflight-

errors=…`

原因:系统自动进行分区

解决:

1
2
3
4
# 临时
swapoff -a
# 永久
sed -ri 's/.*swap.*/#&/' /etc/fstab
  • following as a regular user(Master)
1
2
3
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
  • join master node(node)
1
kubeadm join 172.16.164.136:6443 --token 9oilao.bpbxcm5zkk0jjcgm --discovery-token-ca-cert-hash sha256:609794bd03915be382bdb130c4c180e89cdc863d35cf99be79cf4ddcbfacee24

加入成功,如下图

此时我们在Master节点上使用命令kubectl get nodes查看节点信息:如下图所示

此时的kubectl get nodes的status都是NotNotReady:

查看kubernetes运行状态:

kubectl get pods -n kube-system

如图:

果然,两个Pending犹豫未决

此时我们部署CNI网络,配置如下

1
2
3
# 根据官方文档提示配置CNI网络
kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml
# 报错:The connection to the server raw.githubusercontent.com was refused - did you specify the right host or port? 原因:外网不可访问 -> 在https://www.ipaddress.com/查询raw.githubusercontent.com的真实IP。
1
2
3
sudo vi /etc/hosts
199.232.28.133 raw.githubusercontent.com
# 如下

1
2
3
4
5
# 开启IPVS,修改ConfigMap的kube-system/kube-proxy中的模式为ipvs

kubectl edit cm kube-proxy -n kube-system
# 将空的data -> ipvs -> mode中替换如下
mode: "ipvs"

在此运行kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml成功,如图

此时运行kubectl get nodes效果图如下->成功。(肯能并不一定会立马成功,上面👆确定没问题,请稍等片刻即可)

测试kubernetes

1
2
3
4
5
6
7
# 创建nginx镜像 Create a deployment with the specified name
# kubectl create deployment NAME --image=image -- [COMMAND] [args...] [options]
kubectl create deployment nginx --image=nginx
# 对外暴露端口
kubectl expose deployment nginx --port=80 --type=NodePort
# 查看pod服务
kubectl get pod,svc

成功

技术杂谈

最近刚遇到个问题,我要给自己做的网站加个无限 debugger 反爬,网站是基于 Vue.js 开发的,比如我就想在 main.js 里面加这么一段:

1
2
3
4
setInterval(() => {
debugger
console.log('debugger')
}, 1000)

当时在 Debug 环境下一切好好的,但是到了 build 之后,再运行发现 debugger 就没了,这就神奇了。

我搜了很久,最后终于找到了解决方案,这里记录下。

开发环境和生产环境

这里首先说下 Vue.js 是有开发环境和生产环境之分的,这里它用了一个关键词,叫做 mode。

我们先来看看两个常用的命令:

1
2
npm run serve
npm run build

这两个命令如果大家开发 Vue.js 的话一定不会陌生,但它们是怎么实现的呢?

顺着一找,其实他们定义在了 package.json 里面,是这样的:

1
2
3
4
5
6
7
8
9
10
{
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
},
...
}

这里其实就是调用了 vue-cli-service 的几个命令而已。

vue-cli-service 又是哪里来的呢?很简单,在刚开始初始化项目的时候装了个 Vue CLI:

1
2
3
npm install -g @vue/cli
# OR
yarn global add @vue/cli

它提供了 vue-cli-service 这个命令。

然后我们再来详细看看这个 serve 和 build 命令。

serve

用法如下:

1
2
3
4
5
6
7
8
9
10
11
12
Usage: vue-cli-service serve [options] [entry]

Options:

--open open browser on server start
--copy copy url to clipboard on server start
--mode specify env mode (default: development)
--host specify host (default: 0.0.0.0)
--port specify port (default: 8080)
--https use https (default: false)
--public specify the public network URL for the HMR client
--skip-plugins comma-separated list of plugin names to skip for this run

看到了吧,这里有个 mode,指的就是运行环境,这里默认为 development,即开发环境。

build

用法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Usage: vue-cli-service build [options] [entry|pattern]

Options:

--mode specify env mode (default: production)
--dest specify output directory (default: dist)
--modern build app targeting modern browsers with auto fallback
--no-unsafe-inline build app without introducing inline scripts
--target app | lib | wc | wc-async (default: app)
--formats list of output formats for library builds (default: commonjs,umd,umd-min)
--inline-vue include the Vue module in the final bundle of library or web component target
--name name for lib or web-component mode (default: "name" in package.json or entry filename)
--filename file name for output, only usable for 'lib' target (default: value of --name),
--no-clean do not remove the dist directory before building the project
--report generate report.html to help analyze bundle content
--report-json generate report.json to help analyze bundle content
--skip-plugins comma-separated list of plugin names to skip for this run
--watch watch for changes

这里也有一个 mode,默认就是 production 了,即生产环境。

所以,到这里我们就明白了,调用 build 命令之后,实际上是生产环境了,然后生产环境可能做了一些特殊的配置,把一些 debugger 给去除了,所以就没了。

还原

那咋还原呢?

这里我们就需要用到 Vue.js 的另外一个知识了。

Vue.js 同样是基于 Webpack 构建的,利用了 Webpack 的打包技术,不过为了更加方便开发者配置,Vue.js 在 Webpack 的基础上又封装了一层,一些配置我们不需要再实现 webpack.config.js 了,而是可以实现 vue.config.js,配置更加简单。

在 vue.config.js 里面,它为 Webpack 暴露了几个重要的配置入口,一个就是 configureWebpack,一个是 chainWebpack。

具体的教程大家可以参考官方文档:https://cli.vuejs.org/zh/guide/webpack.html。

比如前者可以这么配置:

1
2
3
4
5
6
7
module.exports = {
configureWebpack: {
plugins: [
new MyAwesomeWebpackPlugin()
]
}
}

后者可以这么配置:

1
2
3
4
5
6
7
8
9
10
11
12
// vue.config.js
module.exports = {
chainWebpack: config => {
config.module
.rule('vue')
.use('vue-loader')
.loader('vue-loader')
.tap(options => {
return options
})
}
}

这里,我们如果要修改 Webpack 构建的一些配置的话,可以利用 chainWebpack。

TerserPlugin

然后,这里又需要引入另外一个 Webpack 插件了,叫做 TerserPlugin,官方介绍链接为 https://webpack.js.org/plugins/terser-webpack-plugin/。

而这个库又是依赖 terser 的,官方介绍链接为 https://github.com/terser/terser。

官方介绍为:

A JavaScript parser and mangler/compressor toolkit for ES6+.

OK,反正就是类似一个 JavaScript 压缩转换器,比如它可以将一些 JavaScript 代码转码、混淆、压缩等等。

这里我们就需要借助于它来实现 debugger 的还原。

这里由于我使用的 Webpack 是 4.x 版本,所以 TerserPlugin 也需要是 4.x 版本,5.x 版本我测试过了不行。

安装配置如下,添加到 package.json 的 devDependencies 里面:

1
"terser-webpack-plugin": "^4.2.3",

然后:

1
npm install

接着 vue.config.js 改写如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const TerserPlugin = require('terser-webpack-plugin')
module.exports = {
...
productionSourceMap: false,
chainWebpack: config => {
config.optimization.minimizer([
new TerserPlugin({
terserOptions: {
mangle: true,
compress: {
drop_debugger: false
}
}
})
]
)
}
}

这里我就保留了 chainWebpack 的配置,然后这里面通过 config 的 optimization 的 minimizer 方法配置了 plugins,然后这里 TerserPlugin 需要声明一个 terserOptions,然后 compress 里面的 drop_debugger 需要设置为 false。

这样,生产环境的 debugger 语句就不会丢了。

技术杂谈

2019 年,网红王尼玛做了一个五分钟的视频,是有关程序员的综艺节目《创造1024》,视频冲上热搜,被转发了几十万次,而这个视频,通篇是对程序员的刻板印象和标签化再加深。

视频里,程序员是这样子的:

视频里面每个程序员都是标配的黑眼圈,更有甚者那个连续七天七夜不眠不休最后累倒的程序员被推崇为 C 位大佬。「没女朋友」、「宅男」、「呆板」、「理工男」、「格子衫」、「没头发」 等等这些标签。

我不是吐槽视频本身,通过这个视频,站在另外的角度去思考,这里面确实有很多让我感觉不那么舒服的地方。

  • 第一,把女性程序员排除在外;
  • 第二,把整个行业的形象刻画的呆板、邋遢;
  • 第三,为什么程序员就一定会被标签化为这样的形象;

有的时候真的非常伤心,互联网时代真的来临了,程序员这个行业走上台前了。

然而走上台前的,不是曾经想用产品改变世界的梦想,而是秃头、加班、格子衫。

看到一些微博还有这样的:

所以一直到现在,我都对格子衫有着非常深刻的生理性反感,我有条纹衬衫,也有花衬衫,但就是不喜欢格子衫,因为每当我去穿格子衫的时候,我就觉得我走进了公众预设定的无聊框架中。因此,每当女朋友陪我买衣服的时候,路过格子衫的区域,我一定不会去选。

嘿嘿没错,我还有女朋友,谁说程序员没有女朋友的?况且,我女朋友还那么好看。

哦哦跑题了,说回格子衫,我现在选择不穿格子衫,其实也是被框架捆绑的一部分,可我又能怎么办呢?

当然,logo卫衣还是很赞。

当然,在提到程序员这个行业的时候,最大也是最让我讨厌的刻板影响,叫程序员安慰师。

程序员安慰师这个所谓的行业和所谓的热搜热转出来的时候,是我觉得最荒谬的事情,到底是谁觉得我们的智商和情商,我们对工作的热情和严谨,是被女性带动的。每个男性都喜欢漂亮的女孩,这是原始的性,但在这之上,我们如此优秀完成工作的事情,是我们聪明的大脑(掐腰)。

最后我想说,我仍然能看到有非常多的调研报告,有越来越多的小姐姐开始在相亲中去倾向程序员这个行业。

但我想说,所谓高薪,所谓单纯,所谓不会出轨,都是外界给我们的一个个贴下的标签,而在这标签下,我们仍然是独立而特别的个体。希望你最后的选择是因为我这个人,而并不是那些无聊的刻板印象。

我们程序员并不一定是秃头、呆滞、毫无生活情趣的死肥宅,至少说很多都不是,我们也有可爱的一面,也有自己热爱的生活,喜欢的人,喜欢的事,请行业不要再这么标签化我们。

1024,我既不想接受格子衫与人体工学椅的安利,也不想接受任何《程序员图鉴》的调侃。我希望节日能让公司反思过高强度的加班,我希望节日能给更多的人传输目前程序届的开源思想,我希望节日能让人们回忆起,在疫情时候,有那么多个体的程序员不记薪资帮助做统计和维护框架,我希望节日能让更多人意识到我们每一个有灵魂的个体本身。

最后,整篇发言稿都是我的一家之言,是我的三观输出,可能会有很多朋友不习惯或者不开心。用程序码出世界,始终是我的理想。

无论如何,祝大家 1024 节日快乐!

使用Hexo编写博客?

亲爱的伙伴您好,很荣幸能与您在此相遇.

本文主要用于记录,在一台完全的“新”机子上,书写Hexo部署博客。

我已经迫不及待想告诉你的几个容易错误的点,你准备好了么?

Let’s Go

在此之前说明,很多东西都是来自网络,当然无论如何还是建议你查阅官方文档。

Hexo官方文档

Fisrt 环境准备

  • [ ] Git
  • [ ] Node.js (Node.js 版本需不低于 10.13,建议使用 Node.js 12.0 及以上版本)
  • [ ] Hexo

Git:

可以参考此文章 https://cuiqingcai.com/9336.html

Mac 用户

如果在编译时可能会遇到问题,请先到 App Store 安装 Xcode,Xcode 完成后,启动并进入 Preferences -> Download -> Command Line Tools -> Install 安装命令行工具。

Node.JS:

Node.js 为大多数平台提供了官方的 安装程序。对于中国大陆地区用户,可以前往 淘宝 Node.js 镜像 下载。

其它的安装方法:

  • Windows:通过 nvs(推荐)或者nvm 安装。
  • Mac:使用 HomebrewMacPorts 安装。
  • Linux(DEB/RPM-based):从 NodeSource 安装。
  • 其它:使用相应的软件包管理器进行安装,可以参考由 Node.js 提供的 指导

Hexo:

所有必备的应用程序安装完成后,即可使用 npm 安装 Hexo。

1
2
3
4
# 和我一样小白的可使用以下命令来安装
npm install -g hexo-cli
# 对于熟悉 npm 的大牛,可以仅局部安装 hexo 包。
npm install hexo

验证安装

1
2
# 注意请不要在项目中使用,可能会因为限制从而导致验证误差
hexo version

Look

Hexo Version

Second 写作

新建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 创建一篇新文章或者新的页面
hexo new [layout] <title>
# EG 注意双引号中的是博客标题,不需要加.md等后缀。
hexo new "Your Title"

# hexo new --help
Usage: hexo new [layout] <title>

Description:
Create a new post.

Arguments:
layout Post layout. Use post, page, draft or whatever you want.
title Post title. Wrap it with quotations to escape.

Options:
-p, --path Post path. Customize the path of the post.
-r, --replace Replace the current post if existed.
-s, --slug Post slug. Customize the URL of the post.

Hexo 有三种默认布局:postpagedraft。在创建者三种不同类型的文件时,它们将会被保存到不同的路径;而您自定义的其他布局和 post 相同,都将储存到 source/_posts 文件夹。

布局 路径
post source/_posts
page source
draft source/_drafts

文内设置

用markdown等编辑器写博客,tags的写法如下:

1
2
# 注意冒号与方括号之间有一个空格,方括号中的标签用英文的”,”
tags: [hexo,备忘录]

添加“阅读全文”按钮

方法一:

1
2
3
4
5
6
7
8
9
10
11
12
13
# 在文章任意你想添加的位置添加
<!--more-->
# EG

---
title: How to use hexo to create blog?
date: 2020-10-17 01:48:53
author:Payne
Mail:wuzhipeng1289690157@gamil.com
tags:[Hexo]
---
<!--more-->
后面的内容在首页不显示,只显示到<!--more-->这里

方法二:

1
2
3
4
# 设置首页文章以摘要形式显示,打开主题配置文件,找到auto_excerpt进行修改
auto_excerpt:
enable: true
length: 150

其中length代表显示摘要的截取字符长度。
注:这两种方法,在博客首页显示的效果不一样,根据自己的需要,选择自己喜欢的方法

请开始您的表演😊

Show Time 。。。

Third 自我查阅

1
2
3
4
5
6
7
8
9
10
11
12
# 构建 -> 用于生成博客的html文件
hexo g
# 预览 -> 用于在本地预览博客,打开浏览器,输入 localhost:4000/ 即可查看。
hexo s
# 检查博客格式等符合要求后,用此命令将博客推送到远端。(需要是自己的才行)
hexo d

# 实操
# 构建并查阅
hexo g && hexo s
# 推送(记得清除缓存!!!)
hexo clean && hexo g && hexo d

images

Fourth 发布

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 详细流程如下
首先clone下来然后,创建分支
git clone "目标地址"
git checkout -b branch_name


# 编写博客



# 构建并查阅
hexo g && hexo s
# 推送(记得清除缓存!!!)
hexo clean && hexo g && hexo d

技术杂谈

Abstract

随着大数据人工智能时代的来临,互联网的快速发展。许许多多以前可能并不那么实际或需要的算法、技术也逐渐进入我们的眼中。例如分布式、集群、负载均衡、也越来越“平民”化。近期重新再一次的对于分布式理念、思想进行了学习。此随笔也因此而来。请多指教 为什么需要分布式? 什么是分布式? 分布式的核心理念是什么? 如何实现分布式、负载均衡、集群?

Why distributed?

为什么需要分布式、集群、负载均衡?

概念的提出或理论的出现,一定是为了解决对应的问题或避免相对应的问题。使其不断的完善化、稳定化。毕竟Human nature is lazy 此时此刻可能更需要什么?(3V3H)

  • Volume 海量
  • Variety 多样
  • velocity 实时
  • 高并发
  • 高可扩
  • 高性能

单机可能将会遇到的问题会有

  • 系统容量
  • 单点故障
  • 性能不足

该如何解决此问题? 有一种比较功利性的思想-缺啥补啥,当然也是比较较为明确的方式。至少我仅仅只需要解决以上中较少问题。 分布式(思想)要解决的问题主要是单机系统中系统容量不足及提高系统可用性。

What‘s Distributed?

什么是分布式?亦或者说满足如何的条件才算分布式?

分布式(系统),简而言之即是由多个处理机通过通信路线互联而构成的松散。一次性近乎解决了所有单点的所有问题。

A distributed system is a system whose components are located on different networked computers, which communicate and coordinate their actions by passing messages to one another.[1] The components interact with one another in order to achieve a common goal. Three significant characteristics of distributed systems are: concurrency of components, lack of a global clock, and independent failure of components.[1] Examples of distributed systems vary from SOA-based systems to massively multiplayer online games to peer-to-peer applications.

个人认为高并发、高可扩、高性能以此为主推到出 并发性:一个大的任务可以划分为若干个子任务,分别在不同的主机上执行 可扩性:可弹性增降容 自治性:分布式系统中的各个节点都包含自己的处理机和内存,各自具有独立的处理数据的功能。通常,彼此在地位上是平等的,无主次之分,既能自治地进行工作,又能利用共享的通信线路来传送信息,协调任务处理 全局唯一性:分布式系统中必须存在一个单一的、全局的进程通信机制,使得任何一个进程都能与其他进程通信,并且不区分本地通信与远程通信。

What is the core idea of distribution?

分布式的核心理念是什么?

较于以上的了解,那么最为重要的为: 一致性:各主从机权限可不同、但需有一致性。建立维持自己的通信 容错性:拥有较为自主的稳定性,可能最大保障系统的正常运行

How to realize distributed?

如何实现分布式?

经过了解与探究,相信你已经抓住的有单机到多机(分布式)的命脉,那就是一致性,信号一致性。如果能想到这里,那么恭喜您,你和我一样还是比较需要去学习的。并没有去否定它的正确与否,仅仅是太过于笼统。 那么既然需要实现分布式,那么仅仅需要建立及维护此通信即可。保障它,你我就从单机的电脑插上了网线。是否有点feel呢?

分布式优缺点剖析

部分先后,仅从不同角度侧重概述。合而为一即可

闪光点: 稳定性:资源共享。若干不同的节点通过通信网络彼此互联,一个节点上的用户可以使用其他节点上的资源。 性能:加快计算速度 机会点 存在通信网络饱和或信息丢失和网络安全问题

RAFT

简介

Raft首先选举出一个server作为Leader,然后赋予它管理日志的全部责任。Leader从客户端接收日志条目,复制给其他server,并告诉他们什么时候可以安全的将日志条目应用到自己的状态机上。拥有一个Leader可以简化replicated log的管理。例如,leader可以决定将新的日志条目放在什么位置,而无需询问其他节点,数据总是简单的从leader流向其他节点。Leader可能失败或者断开连接,这种情况下会选出一个新的leader。 通过leader,Raft将一致性问题分解成三个相当独立的子问题:

  • Leader Election:当集群启动或者leader失效时必须选出一个新的leader。
  • Log Replication:leader必须接收客户端提交的日志,并将其复制到集群中的其他节点,强制其他节点的日志与leader一样。
  • Safety:最关键的安全点就是图3.2中的State Machine Safety Property。如果任何一个server已经在它的状态机apply了一条日志,其他的server不可能在相同的index处apply其他不同的日志条目。后面将会讲述raft如何实现这一点。

基础

一个Raft集群会包含数个server,5是一个典型值,可以容忍两个节点失效。在任何时候每个server都会处于Leader、Candidate、Follower三种状态中的一种。在正常情况下会只有一个leader,其他节点都是follower,follower是消极的,他们不会主动发出请求而仅仅对来自leader和candidate的请求作出回应。leader处理所有来自客户端的请求(如果客户端访问follower,会把请求重定向到follower)。Candidate状态用来选举出一个leader。 多个candidate想要成为leader,如果一个candidate赢得选举,它将会在剩余的term中作为leader。在一些情况下选票可能会被瓜分,导致没有leader产生,这个term将会以没有leader结束,一个新的term将会很快产生。Raft确保每个term至多有一个leader。Term在Raft中起到了逻辑时钟的作用,它可以帮助server检测过期信息比如过期的leader。每一个server都存储有current term字段,会自动随时间增加。当server间通信的时候,会交换current term,如果一个节点的current term比另一个小,它会自动将其更新为较大者。如果candidate或者leader发现了自己的term过期了,它会立刻转为follower状态。如果一个节点收到了一个含有过期的term的请求,它会拒绝该请求。 Raft节点之间通过RPC进行通信,基本的一致性算法仅仅需要两种RPC。RequestVote RPC由candidate在选举过程中发出,AppendEntries RPC由leader发出,用于复制日志和提供心跳。每一个请求类型都有对应的response,Raft假定request和response都可能会丢失,因此要求请求者有超时重试的能力。为了性能,RPC请求会并行发出,而且不保证RPC的到达顺序。

Leader election

Raft使用心跳机制来触发leader选举。当server启动的时候是处于follower状态,当它可以收到来自leader或者candidate的有效RPC请求时就会保持follower的状态。Leader发送周期性的心跳(不含日志的AppendEntries RPC)给所有的follower来确保自己的权威。如果一个follower一段时间(称为election timeout)没有收到消息,它就会假定leader失效并开始新的选举。 为了开始新一轮选举,follower会提高自己当前的term并转为candidate状态。它会先给自己投一票然后并行向集群中的其他server发出RequestVote RPC,candidate会保持这个状态,直到下面三种事情之一发生:

  1. 赢得选举。当candidate收到了集群中相同term的多数节点的赞成票时就会选举成功,每一个server在给定的term内至多只能投票给一个candidate,先到先得。收到多数节点的选票可以确保在一个term内至多只能有一个leader选出。一旦一个candidate赢得选举,它就会成为leader。它之后会发送心跳消息来建立自己的权威,并阻止新的选举。
  2. 另一个server被确定为leader。在等待投票的过程中,candidate可能收到来自其他server的AppendEntries RPC,声明它才是leader。如果RPC中的term大于等于candidate的current term,candidate就会认为这个leader是合法的并转为follower状态。如果RPC中的term比自己当前的小,将会拒绝这个请求并保持candidate状态。
  3. 没有获胜者产生,等待选举超时。candidate没有选举成功或者失败,如果许多follower同时变成candidate,选票就会被瓜分,形不成多数派。这种情况发生时,candidate将会超时并触发新一轮的选举,提高term并发送新的RequestVote RPC。然而如果不采取其他措施的话,选票将可能会被再次瓜分。

Raft使用随机选举超时来确保选票被瓜分的情况很少出现而且出现了也可以被很快解决。election timeout的值会在一个固定区间内随机的选取(比如150-300ms)。这使得在大部分情况下仅有一个server会超时,它将会在其他节点超时前赢得选举并发送心跳。candidate在发起选举前也会重置自己的随机election timeout,也可以帮助减少新的选举轮次内选票瓜分的情况

Log Replication

一旦一个leader被选举出来,它开始为客户端请求服务。每一个客户端请求都包含着一个待状态机执行的命令,leader会将这个命令作为新的一条日志追加到自己的日志中,然后并行向其他server发出AppendEntries RPC来复制日志。当日志被安全的复制之后,leader可以将日志apply到自己的状态机,并将执行结果返回给客户端。如果follower宕机或运行很慢,甚至丢包,leader会无限的重试RPC(即使已经将结果报告给了客户端),直到所有的follower最终都存储了相同的日志。 日志按下图的方式进行组织,每一条日志储存了一条命令和leader接收到该指令时的term序号。日志中的term序号可以用来检测不一致的情况,每一条日志也拥有一个整数索引用于定位。

总结

RAFT算法终究实现了什么?

  • 实现了信号一致性
  • 动态的Leader、Accessible or work 角色及节点管理,最大限度的保障了稳定性。
  • 他的核心为保障大多数都可用,即可正常运行。

寄语: 如果您是一位非常有经验的管理者或您有相对稳健的算法与数据结构基础, 是否与您所存在的了解的结构有所大多相似呢?-有效管理 直接上司管辖直属同事,算法中的BFS 在稍稍加入较为智能的选Leader,若大多数leader停止了工作,worker 将会进行相对应的“休息”,并没有接受到任务,简单的计算机选择做不如不做。毕竟Human nature is lazy

技术杂谈

Docker File 解析:

构建简史

编写一个dockerfile的文件,符合dockerfile的规范 docker build 执行,获得一个自定义的镜像 docker 运行 Docker执行docker file文件的大致流程

docker 从基础镜像运行一个容器 执行一条指令并对容器作出修改 执行类似docker commit的操作提交一个新的镜像层 docker 在基于刚提交的镜像运行一个新容器 执行docker 中的下一条指令知道所有指令都执行完成

Docker File基础知识:

每条保留字指令必须为大写字母且后面要跟随至少一个参数 指令从上到下、从左至右执行 ‘#’ :表示注释 每条指令都会创建一个新的镜像层,并对镜像进行提交

Docker File 体系结构:

保留字指令:

FROM:基础镜像,当前这个新的镜像是基于哪个镜像(scratch) MAINTAINER:镜像作者+邮箱 RUN:容器构建时所需要运行的命令 EXPOSE:当前容器对外暴露的端口号 WORKDIR:指定在创建容器后,终端默认登陆的进来工作目录,一个落脚点 ENV:用于构建镜像过程中设置环境变量 ADD: 拷贝加解压缩:将宿主机目录下的文件拷贝进镜像且add命令会自动处理rul和解压tar压缩包 COPY:将从构建上下文目录中<源路径>的文件/目录复制到新的一层的镜像内的<目标路径>位置

  • COPY src dest
  • COPY [“src”,”dest”]

VOLUME:容器数据卷,用于数据保存和持久化工作 CMD:

  • 指定一个容器启动时要运行的命令
  • DockerFile 中可以有多个CMD指令,但只有最后一个生效,CMD会被docker run 之后的参数替换!!!

ENTRYPOINT:

  • 指定一个容器启动时要运行的命令
  • 目的与CMD一样,都是在指定容器启动程序及参数
  • 不会被替换,被追加

ONBUILD:触发器 当构建一个被继承Docker File时运行的命令,父镜像在被子继承后父镜像的onbuild被触发

关键字详解

Dockerfile 分为四部分:

基础镜像信息、维护者信息、镜像操作指令和容器启动时执行指令。

基础镜像信息

FROM

1
2
# 格式
FROM <image> or FROM <image>:<tag>

如果在同一个Dockerfile中创建多个镜像时,可以使用多个 FROM 指令(每个镜像一次)

注意: Dockerfile每个保留字都会在docker容器中新建一层镜像层, 合理的减少镜像层以达节省资源的目的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# base
FROM python:3.7
RUN pip3 install requests
RUN pip3 install bs4
RUN pip3 install scrapy
RUN ...

# other
FROM python:3.7
RUN pip3 install requests bs64 scrapy # Python通过空格分隔,达到安装多个包的效果

# batter
# 外部建立所安装包
pip3 freeze >> requirements.txt
# 在Dockerfile 中
FROM python:3.7
RUN pip3 install -r requirements.txt

分析: 达到的效果虽相同,但所用资源却不同, base 所使用的资源约为other,batter的3/5

  • base 三层
  • other 一层
  • batter 一层
  1. 若所需要使用到的第三方库较少,建议使用other
  2. 在实际工程中用到的第三方库大多情况下都大于三个,方法二虽好.但所需导入的包一多,很可能出现单词拼写错误, 格式不符

维护者信息(非必须)

MAINTAINER

1
2
# 格式
MAINTAINR <name>

镜像操作指令(按需择选)

COPY

作用: 复制文件指令,从上下文 目录中复制或目录, 到容器中的指定路径 同等需求下建议使用COPY

1
2
3
4
5
6
# 格式
COPY <src> <dest>
COPY [--chown=<user>:<group>] <源路径1>... <目标路径>
COPY [--chown=<user>:<group>] ["<源路径1>",... "<目标路径>"]
# [--chown=<user>:<group>]:可选参数,用户改变复制到容器内文件的拥有者和属组。
# 复制本地主机的 `<src>`(为 Dockerfile 所在目录的相对路径)到容器中的 `<dest>`

<源路径>:源文件或者源目录,这里可以是通配符表达式,其通配符规则要满足 Go 的 filepath.Match 规则。

1
2
COPY hom* dir/
COPY hom?.txt dir/

<目标路径>:容器内的指定路径,该路径不用事先建好,路径不存在的话,会自动创建。

ADD

作用: 复制文件指令,从上下文 目录中复制或目录, 到容器中的指定路径 同等需求下建议使用COPY

1
2
3
4
5
# 格式 
ADD <src> <dest>`

# 该命令将复制指定的 `<src>` 到容器中的 `<dest>`。
# 其中 `<src>` 可以是Dockerfile所在目录的一个相对路径;也可以是一个 URL;还可以是一个 tar 文件(自动解压为目录)。
  • ADD 的优点:在执行 <源文件> 为 tar 压缩文件的话,压缩格式为 gzip, bzip2 以及 xz 的情况下,会自动复制并解压到 <目标路径>。
  • ADD 的缺点:在不解压的前提下,无法复制 tar 压缩文件。会令镜像构建缓存失效,从而可能会令镜像构建变得比较缓慢。具体是否使用,可以根据是否需要自动解压来决定

小结:

  • ADD 与 COPY 功能无明显差异, 但针对性不同
  • 当使用本地文件为源目录时,建议使用Copy
  • 当需使用压缩包中文件时构建时,建议使用Copy

RUN

作用: 用于在容器内执行命令

1
2
3
# 格式
RUN <command> [option] # 相当于shell格式
RUN ["command", "option1", 'option2'] # 相当于 exec 格式

WORKDIR

作用:指定工作目录。用 WORKDIR 指定的工作目录,会在构建镜像的每一层中都存在。(WORKDIR 指定的工作(主)目录,必须是提前创建好)。 docker build 构建镜像过程中的,每一个 RUN 命令都是新建的一层。只有通过 WORKDIR 创建的目录才会一直存在。

1
2
# 格式
WORKDIR <dir>

USE

作用:用于指定将使用命令的用户和用户组 此处只是切换后续命令执行的用户(用户和用户组必须提前已经存在)。 由于docker 无设置,默认需在root权限下运行 正所谓权限越大能力越大,若被入侵则造成损失较于其他权限为最大 安全与灵活性 二者相对斟酌

1
2
# 格式
USER <username>[:<usergroup>]

EXPOSE

作用:仅声明端口

  • 方便配置映射
  • 在运行时使用随机端口映射时,也就是 docker run -P 时,会自动随机映射 EXPOSE 的端口。
1
2
# 格式
EXPORT <port1> [<prot2> <prot3> ...]

执行指令

CMD

作用: 类似于 RUN命令,但运行处不同

  • RUN 在Docker build 前运行
  • CMD 在docker run 后运行

注意:

  • 当 Dockerfile 中存在多个 CMD 指令,仅最后一个生效。
  • CMD 指令指定的程序可被 docker run 命令行参数中指定要运行的程序所覆盖。

当使用自定制镜像时,大致流程为

  • 编写业务代码
  • 构建定制镜像(docker build)
  • docker 中运行业务 (Docker run)
1
2
3
4
5
# 格式
CMD <ShellCommand1 [option]> [&&ShellCommand2[option] ]
CMD ["<可执行命令或文件>", "<option1>", "<option2>", ... ]
# 此写法为保留字 ENTRYPOINT 指令指定的程序提供默认参数
CMD ["<command1>", "<command2>", "<command3>", ...]

ENTRYPOINT

作用: 功能与CMD相似,但不会被docker run 后的指定参数所覆盖, 命令行参数会被当作参数送给 ENTRYPOINT 指令指定的程序。 但是当docker run 使用了 —entrypoint 选项此选项的参数,可当作要运行的程序覆盖 ENTRYPOINT 指令指定的程序。 ENTRYPOINT CMD对比 同: 多个 指令,仅最后一个生效。 异: ENTRYPOINT选项的参数可当作要运行的程序覆盖 ENTRYPOINT 指令指定的程序。

1
2
# 格式
ENTRYPOINT ["<可执行命令或文件>","option1", "option2"]

ENV

作用:配置容器内的环境变量,且保存,可以被后续 指令使用

1
2
3
# 格式
ENV <Key> <Values>
ENV <Key1>=<Values1>, <Key2>=<Values2> ...

ARG

作用:配置容器内的环境变量,且保存,可以被后续 指令使用(与ENV功能相似) 不同:作用域不一样。ARG 设置的环境变量仅对 Dockerfile 内有效 也就是说只有 docker build 的过程中有效,构建好的镜像内不存在此环境变量。

1
构建命令 docker build 中可以用 --build-arg <参数名>=<值> 来覆盖。
1
2
3
# 格式
ARG <key>[=默认值], <Value>
# 若不写Value 则Build时为自己写的默认值

VOLUME

作用:定义匿名数据卷。在启动容器时忘记挂载数据卷,会自动挂载到匿名卷。

  • 避免重要的数据,因容器重启而丢失
  • 避免容器不断变大
1
2
3
# 格式:
VOLUME ["<路径1>", "<路径2>"...]
VOLUME <路径>

在启动容器 docker run 的时候,我们可以通过 -v 参数修改挂载点。

HEALTHCHECK

作用: 用于指定某个程序或者指令来监控 docker 容器服务的运行状态。

格式:

1
2
3
HEALTHCHECK [option] CMD <command> #设置检查容器健康状况的命令
HEALTHCHECK NONE # 如果基础镜像有健康检查指令,使用这行可以屏蔽掉其健康检查指令
HEALTHCHECK [option] CMD <command> # CMD 后面跟随的命令使用

ONBUILD

作用: 用于延迟构建命令的执行。 先构建一个父类镜像(ONBUILD 在父类中,但不立即执行),后子类继承此父类镜像(执行父类的ONBUILD 命令) 本次并不执行,当镜像调用它时,将执行父类中ONBUILD命令

1
2
# 格式
ONBUILD <其它指令>

技术杂谈

Docker容器数据卷

Docker理念:

  • 将运用与运行的环境打包形成容器运行,运行可以伴随着容器,但由于对数据要求希望是持久化的
  • 容器之间希望可以共享数据

一、Docker容器数据卷是什么?

  • docker容器产生的数据,如果不通过docker commit生成新的镜像,使数据做为镜像的一部分保存下来,那么删除容器之后,数据也随之被删除。为了能保存数据在docker中,我们使用容器卷。
  • 好比从电脑(docker)中拷贝数据(使用的U盘-容器卷)

二、Docker容器数据卷能干什么?

  • 数据的持久化
  • 容器间继承+共享数据

特点:

  • 容器之间共享过重用数据
  • 卷中更改可之间生效
  • 数据卷中的更改不会在镜像的更新中
  • 数据中的更改不会包含在镜像的更新中
  • 数据卷的生命周期一直持续到没有容器使用止

三、Docker容器数据卷

容器内添加:

1
2
docker run -it -v /[宿主机绝对路径目录]:[/容器内目录] [container_ID]
docker run -it -v /myDataVolume:/dataVolumeContainer [镜像名]

权限报错处理:

1
docker run -it -v /myDataVolume:/dataVolumeContainer --privileged=true [镜像名]

检测是否数据卷是否挂载成功

1
docker inspect [container_ID]

容器停止后,主机修改后数据是否同步 可以!但需为同一个容器!!![The same container_id]

1
2
3
使用docker ps -l 查看运行过的容器信息
# 查看运行容器信息
docker ps -l

命令(带权限):容器中只读,不可修改 docker run -it -v /宿主机绝对路径:/容器内目录:ro 镜像名

dockerfile 添加

javaEE:hello.java —-> hello.class Docker: images ===》 DockerFile

  1. 新建mydocker文件夹并进入
  2. 在dockerfile中使用volume指令来给镜像添加一个或多个数据卷
    • volume[“/dataVolumeContainer”,”/dataVolumeContainer2”,”/dataVolumeContainer3”]
  3. File构建

    1
    2
    3
    4
    5
    # volume test
    FROM centos
    VOLUME ["/dataVolumeContainer1","/dataVolumeContaine2"]
    CMD echo "finished,----sucess1"
    CMD /bin/bash
  4. build后生成镜像

    1
    2
    3
    4
    docker build -f /路径/文件名 -t 容器名:TAG . 
    -f: file-->指定为文件
    -t:为容器重新分配一个为输入终端;
    . : 分布执行file中命令

四、Docker容器数据卷容器

命名的容器挂载数据卷,其他容器通过挂载这个(父容器)实现数据共享,挂载数据卷的容器称之为数据卷容器 容器间传递共享(volumes-from)

dc01(主) 删除后 dc02(子1) dc03(子2)不受影响,dc02与dc3 继续传输

结论:容器之间配置信息的传递,数据卷的生命周期一直持续到没有容器使用为止

技术杂谈

搭建个人Leanote云笔记

Leanote是一款在线的云笔记应用,有如下特点:

  • 支持网页、PC、手机APP客户端和微信版,随时记录,方便分享,支持语音,图片输入。
  • 代码高亮,涵盖所有主流语言的代码高亮,随心所欲在Leanote里写代码,记知识。
  • Markdown 编辑器,实时同步预览。
  • 专业数学公式编辑,像Word和Latex能编辑数学公式。
  • 支持创建思维脑图,将散乱的想法以树状信息分层展示。
  • 详细历史纪录,每次保存都在后端备份,轻松查找,一键恢复。
  • 实时同步云端。

简介

基于Linux + Mongo + Leanote 快速搭建个人云笔记

安装MongoDB

MongoDB是一个基于分布式文件存储的高性能数据库,介于关系数据库和非关系数据库之间,它支持的数据结构非常松散是类似于json和bson格式,因此可以存储比较复杂的数据类型。Mongo最大的特点是它支持的查询语言非常强大,其语法有点类似于面向对象的查询语言,几乎可以实现类似关系数据库单表查询的绝大部分功能,而且还支持对数据建立索引。 Leanote云笔记使用MongoDB作为后端数据库,按照以下步骤按照MongoDB数据库。

1
2
3
4
5
6
# install MongoDB
yum -y install mongodb mongodb-server.x86_64 mariadb-devel.i686
# start MongoDB service
systemctl start mongod
# Verify mongodb running state
systemctl status mongod

安装Leanote

1
2
3
4
5
6
7
8
# Download Leanote installation package
wget https://nchc.dl.sourceforge.net/project/leanote-bin/2.6.1/leanote-linux-amd64-v2.6.1.bin.tar.gz
# Unzip
tar -zxvf leanote-linux-amd64-v2.6.1.bin.tar.gz
# setting leanote
vim leanote/conf/app.conf
# change app.secret
app.secret = Self configuration

配置服务

If the following message appears

Failed global initialization: BadValue Invalid or no user locale set. Please ensure LANG and/or LC_*

Please configure the environment variables as follows

1
export LC_ALL=C
1
2
3
4
# init MongoDB
mongorestore -h localhost -d leanote --dir /root/leanote/mongodb_backup/leanote_install_data/
# Start service
nohup bash /root/leanote/bin/run.sh > /root/leanote/run.log 2>&1

访问笔记

在浏览器中访问服务器弹性地址:端口(默认9000)

1
2
3
# 默认账号
username: admin
password: abc123

技术杂谈

Multiple solutions of Fibonacci (Python or Java)

本章是用英文写的,作为或想成为一名优秀的攻城狮,习惯阅读英文文档将使你受益良多。例如更好的查看最新版的官方文档、与国外友人交流、等等 其实英文的生词也并不多,其中90%的英文都在代码里,当然这其中的精华也在代码里,代码相信大部分伙计还是都可以看懂.所以,请不要惊慌。对于English,让我们一起取克服它、习惯它、拥抱它。然后把它锤倒在地,相信你可以的。 GO, Go, GO 如果实在不行,各种页面翻译来一手。莫慌,这都是小场面,啥都不是事儿,好吧

Violence law(Top-down)

It can be solved directly according to the known conditions (f (0) = 0, f (1) = 1 F(N) = F(N - 1) + F(N - 2), for N > 1)

Python Code

1
2
3
4
class Solution:
def fib(self, N: int) -> int:
if N == 1 or N == 2: return N
return self.fib(N - 1) + self.fib(N - 2)

Java Code

1
2
3
4
5
6
7
8
9
10
11
12
class Solution {
public int fib(int N) {
if (N == 1 || N == 2) return 1;
return fib(N - 1) + fib(N - 2);
}
}

class Solution {
public int fib(int N) {
return N < 2 ? N : fib(N - 1) + fib(N - 2);
}
}

Violence law add cache(Pruning)

We know that if we don’t do any processing, we will repeat too many calculations, which is very bad The processing idea will avoid repeated calculation

Python Code

1
2
3
4
5
class Solution2:
@functools.lru_cache()
def fib(self, N: int) -> int:
if N <= 1: return N
else: return self.fib(N - 1) + self.fib(N - 2)

Java Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Solution {
private Integer[] cache = new Integer[31];
public int fib(int N) {
if (N <= 1) return N;
cache[0] = 0;
cache[1] = 1;
return memoize(N);
}
public int memoize(int N) {
if (cache[N] != null) return cache[N];
cache[N] = memoize(N-1) + memoize(N-2);
return memoize(N);
}
}

Divide and conquer solution

Recursion, iteration, divide and conquer, backtracking, they do not have a clear distinction Recursion:The core idea is to govern separately and unify the officials

1
2
3
4
5
6
7
class Solution:
def fib(self, N: int) -> int:
memo = {}
if N < 2: return N
if N-1 not in memo: memo[N-1] = self.fib(N-1)
if N-2 not in memo: memo[N-2] = self.fib(N-2)
return memo[N-1] + memo[N-2]

Dynamic recursion(Bottom up)

Basic solutions

More initial value, continuous dynamic recursive

Python Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Solution:
def fib(self, N: int) -> int:
if N < 2: return N
dp = [0 for _ in range(N + 1)]
dp[0], dp[1] = 0, 1
for i in range(2, N + 1):
dp[i] = dp[i - 1] + dp[i - 2]
return dp[- 1]

class Solution:
def fib(self, N: int) -> int:
if N == 0: return 0
memo = [0,1]
for _ in range(2,N+1):
memo = [memo[-1], memo[-1] + memo[-2]]
return memo[-1]
Java Code
1
2
3
4
5
6
7
8
9
10
11
12
13
class Solution {
public int fib(int N) {
if (N <= 1) return N;
if (N == 2) return 1;
int curr = 0, prev1 = 1, prev2 = 1;
for (int i = 3; i <= N; i++) {
curr = prev1 + prev2;
prev2 = prev1;
prev1 = curr;
}
return curr;
}
}

Use better base types (tuples) to improve performance

1
2
3
4
5
6
7
class Solution:
def fib(self, N: int) -> int:
if N == 0: return 0
memo = (0,1)
for _ in range(2,N+1):
memo = (memo[-1], memo[-1] + memo[-2])
return memo[-1]

Better solutions

Python Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Solution:
def fib(self, N: int) -> int:
curr, prev1, prev2 = 0, 1, 1
for i in range(3, N + 1):
curr = prev1 + prev2
prev2 = prev1
prev1 = curr
return curr

class Solution5:
def fib(self, N: int) -> int:
prev, now = 0, 1
for i in range(N):
prev, now = now, now + prev
return prev

Java Code

1
2
3
4
5
6
7
8
9
10
11
12
13
class Solution {
public int fib(int N) {
if (N == 0) return 0;
if (N == 2 || N == 1) return 1;
int prev = 1, curr = 1;
for (int i = 3; i <= N; i++) {
int sum = prev + curr;
prev = curr;
curr = sum;
}
return curr;
}
}

Mathematical conclusion method

Python Code

1
2
3
4
5
class Solution:
def fib(self, N: int) -> int:
sqrt5 = 5 ** 0.5
fun = pow((1 + sqrt5) / 2, n + 1) - pow((1 - sqrt5) / 2, n + 1)
return int(fun / sqrt5)

Java Code

1
2
3
4
5
6
class Solution {
public int fib(int N) {
double sqrt5 = (1 + Math.sqrt(5)) / 2;
return (int)Math.round(Math.pow(sqrt5, N)/ Math.sqrt(5));
}
}

Other

在计算机的世界中由最基本的for loop、while loop、if…else无限衍生,无论多么复杂的逻辑最后大多可归纳为以上三种。当然除非原本逻辑无重复性,无条件分支。

一、循环(重复)

不断的重复、有始有终 循环实现

1
2
3
4
5
6
7
private loop(){
for(start; end; loop termination){
expression1;
expression2;
expression3;
}
}
1
2
3
4
5
def loop():
for start in end/loop_termination:
expression1;
expression2;
expression3;

二、递归


特征:自相似性、有始有终 实现:归去来兮、适可而止 何时想到递归?

子问题与原始问题做同样的事

递归实现:

1
2
3
4
5
6
7
8
9
10
11
12
private void recursion(int level,int param1,int param2...):{
// 终止条件(recursion terminato)
if(level > MAX_LEVEL){
# process_rsult
return
}
// 处理此层过程逻辑(process logic in current level)
process(level, data1, data2...)
// 进入下一层(dill down)
recursion(level: level + 1, newParam):
// 如果需要恢复此层状态
}
1
2
3
4
5
6
7
8
9
10
def recursion(level, param1, param2...):
# 终止条件(recursion terminato)
if level > MAX_LEVEL:
# process_rsult
return
# 处理此层过程逻辑(process logic in current level)
process(level, data1, data2...)
# 进入下一层(dill down)
self.recursion(level + 1, param1, param2...):
# 如果需要恢复此层状态

二、分治


定义:分而治之,群臣归一 何时想到分治?

当复杂的问题可以拆分成简单的子问题

分治实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private static int divide_conquer(Problem, Param1, Param2...) {
// 终止条件
if (problem == NULL) {
int res = process_last_result();
return res;
}
// 拆分子问题
subProblems = split_problem(problem)

res0 = divide_conquer(subProblems[0])
res1 = divide_conquer(subProblems[1])
...
// 合并子问题结果
result = process_result(res0, res1);

return result;
}
1
2
3
4
5
6
7
8
9
10
11
12
def divide_conquer(Problem, Param1, Param2...):
# 终止条件
if problem is None:
return
# 拆分子问题
subproblems = split_problem(problem, data)
subresult1 = self.divide_conquer(subproblems[0], p1, ...)
subresult2 = self.divide_conquer(subproblems[1], p1, ...)
subresult3 = self.divide_conquer(subproblems[2], p1, ...)
...
# 合并子问题结果
result = process_result(subresult1, subresult2, subresult3, …)

三:回溯


采用“试错”思想,尝试“分步”去解决问题。在分步的过程中。根据上层结果,尝试此层最优解决此问题,如果此层较于上层不是最优则回溯。

四、DP(Dynamic programming) 动态规划/动态递推


定义

In both contexts it refers to simplifying a complicated problem by breaking it down into simpler sub-problems in a recursive manner. While some decision problems cannot be taken apart this way, decisions that span several points in time do often break apart recursively. 在这两种情况下,它都是指通过递归的方式将复杂问题分解为更简单的子问题来简化它。虽然有些决策问题不能用这种方式分解,但是跨越多个时间点的决策通常会递归地分解。 Simplifying a complicated problem by breaking it down into simpler sub problem(in a recursibe manner) 把一个复杂的问题分解成更简单的子问题简化它(用一种递归的方式)

自低向上 动态规划关键点:

  • 最优子结构
  • 储存中间状态
  • 递推公式(状态转移方程,DP方程)
  • eg
1
2
3
4
5
# 一维
Fib:
opt[i] = opt[n - 1] + opt[n - 2]
# 二维
opt[i][j] = opt[i + 1][j] + opt[i][j + 1]

以斐波那契数列为例:

1
2
3
F(0) = 0, F(1) = 1 

F(N) = F(N - 1) + F(N - 2)(N >= 2

递归(傻递归): 若计算F(4);需计算

1
2
lin1 F(4) = f(3)、f(2), 
lin2 F(3):f(2)、f(1), F(2) = f(1) + f(0)

DP:

1
2
i(0) = 0, i(1) = 1
[0, 1, 1, 2, 3, 5]

总结

动态规划、递归、分治、无本质区别 共性: 重复子问题 异性:最优子结构、中途淘汰次优

技术杂谈

刚装了台新机器,Git 显示总是呈现这样的样子:

1
"\346\265\213\350\257\225.txt"

解决办法:

1
git config --global core.quotepath false

仅此记录,完毕。

技术杂谈

设计模式(Design Patterns),旨在软件设计(可重用的面向对象软件的要素)中,被反复使用的一种代码设计经验。设计模式旨在简化代码量、降低耦合度、高效使用可重用代码,提高代码可拓性和可维护性。

3V3H概念: 3V: Voluem(海量),Variety(多样)、Velocity(实时) 3H: High concurrency(高并发)、High performance(高性能)、High development(高可拓)

设计模式的由来:

设计模式这个术语是由上个世纪90年代Erich Gamma、Richard Helm、Raplh Johnson和Jonhn Vlissides四人总结提炼而出。并编写了Design_Patterns(设计模式)一书,他们四位统称为GOF(俗称四人帮)。 设计模式:即 将常使用的设计思想提炼出一个个模式,然后给每个模式命名,这样在使用的时候更方便交流。GOF把23个常用模式分为创建型模式、结构型模式和行为型模式三类

为何采用设计模式思想?(请注意思想二词!!!)

设计模式并非直接用来完成编码,而是描述在各种不同情况下,要怎么解决问题的一种方案一种思想 面向对象设计模式常以对象来描述其中的关系和相互作用,但不涉用来完成应用程序的特定类别或对象。设计模式能使不稳定依赖于相对稳定、具体依赖于相对抽象,避免会引起麻烦的紧耦合,以增强软件设计面对并适应变化的能力。 学习设计模式,关键是学习设计思想,不为设计而设计,需合理平衡设计模式的复杂度和灵活性。 须知设计模式并不是万能的!!!

设计模式符合什么原则?

开闭原则(Open Colse Principle):

软件对拓展开发,对修改关闭。

理解:在增加新功能的时候,能不改代码就尽量不要改,如果只增加部分代码便可完成新功能,即是最好。

里氏替换原则(Liskov Substitution Principle):

面向对象设计原则(六大原则)

理解:若调用父类方法可成功,即调用成调用其子类亦可成功

面向对象六大设计原则:

英文名称

缩写

中文名称

Single Responsibility Principle

SRP

单一职责原则

Open Close Principle

OCP

开闭原则

Liskov Substitution Principle

LSP

里氏替换原则

Law of Demeter ( Least Knowledge Principle)

LoD

迪米特法则(最少知道原则)

Interface Segregation Principle

ISP

接口分离原则

Dependency Inversion Principle

DIP

依赖倒置原则

常用设计模式概况:

[gallery columns=”1” size=”full” ids=”9642”]

创建型模式(Creational Patterns):

如何创建对象? 核心思想是要把对象的创建和使用分离,从而使得二者可以相对独立地变换。这些模式更加关注对象之间的创建

  • 单例模式:Singleton Pattern
  • 多例模式:Multiton Pattern
  • 工厂模式:Factory Pattern
  • 静态工厂模式:Static Factory Pattern
  • 抽象工厂模式:Abstract Factory Pattern
  • 原型模式:Prototype Pattern
  • 建造者模式:Builder Pattern
  • 对象池模式:Pool Pattern

结构型模式(Structural Patterns):

如何组合各种对象,以便于更好、更灵活的结构?面向对象的继承机制虽提供了基本的子类继承与拓展父类的功能,但结构型模式却不仅是简单的继承,还有更多的通过组合,使之与运行期的动态组合实现更加灵活的功能。这些模式更加关注对象之间的组合

  • 组合模式(Composite)
  • 桥接模式(Bridge)
  • 适配器模式(Adapter)
  • 过滤器模式(Filter、Criteria Pattern
  • 装饰模式(Decorator)
  • 外观模式(Facade)
  • 门面模式:Facade
  • 享元模式(Flyweight)
  • 代理模式(Proxy)
  • 数据映射模式:Data Mapper
  • 依赖注入模式:Dependency Injection
  • 流接口模式:Fluent Interface
  • 注册模式:Registry

行为模式(Behavioral Patterns):

行为模式主要涉及对象与函数(算法)之间的职责分配,通过对象及函数灵活组合。此种模式更加关注对象之间的通信 责任链模式:Chain of Responsibility Pattern 命令模式:Command Pattern 解释器模式:Interpreter Pattern 迭代器模式:(Iterator Pattern 中介者模式:Mediator Pattern 备忘录模式:Memento Pattern 观察者模式:Observer Pattern 状态模式:State Pattern 空对象模式:Null Object Pattern 策略模式:Strategy Pattern 模板模式:Template Pattern 访问者模式:Visitor Pattern 规格模式:Specification

有不少的人说程序= 算法+ 数据结构,但个人认为程序 = 架构 + 设计模式 + 数据结构与算法 接下来将一起对本文中面向对象原则及设计模式进行详细的学习,同时我也会不断更新算法与数据结构相关的知识,让我们一起学习起来

技术杂谈

官方文档说明

在设置中找到ITEM_PIPELINES并加入以下代码

1
scrapy.pipelines.images.ImagesPipeline: 301

settings配置:

图片存储路径:

1
IMAGES_STORE = “your path”

图片存储天数

1
images_EXPIRES =  30

设置缩略图(固定值):

1
2
3
4
IMAGES_THUMBS = {
'small':(50,50)
'big':(270,270)
}

示例:

1
2
3
# 配置图片管道参数
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
IMAGES_STORE = os.path.join(BASE_DIR,'images')
1
2
3
4
5
6
7
8
# 寻找此文件的父级目录
os.path.dirname()
# 当前脚本的绝对路径目录
os.path.abspath(__file__)
# __file__当前脚本的名字

IMAGES_STORE = os.path.join(BASE_DIR,'images')
将BASE_DIR新增IMAGES文件夹路径

设置spider中获取images_url的提取方法

1
2
3
item['image_urls'] = "提取语法"
# item['image_urls'] = response.css(".pic img:attr('src')").extract()
item['images'] = [] # 【】中不需要填写,下载图片之后,保存本地的文件位置

使用ImagesPipeline下载图片时,需要使用images_urls字段,images_urls一般是可迭代的列表或元组类型

如果遇到图片反扒请打开

1
2
3
4
5
# DEFAULT_REQUEST_HEADERS = {
# 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
# 'Accept-Language': 'en',
# "referer":"自行配置"
# }

存入MongoDB,示例代码

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
 import pymongo
from itemadapter import ItemAdapter

class MongoPipeline:

collection_name = 'scrapy_items'

def __init__(self, mongo_uri, mongo_db):
self.mongo_uri = mongo_uri
self.mongo_db = mongo_db

@classmethod
def from_crawler(cls, crawler):
return cls(
mongo_uri=crawler.settings.get('MONGO_URI'),
mongo_db=crawler.settings.get('MONGO_DATABASE', 'items')
)

def open_spider(self, spider):
self.client = pymongo.MongoClient(self.mongo_uri)
self.db = self.client[self.mongo_db]

def close_spider(self, spider):
self.client.close()

def process_item(self, item, spider):
self.db[self.collection_name].insert_one(ItemAdapter(item).asdict())
return item

更多详情请查阅官方文档:https://docs.scrapy.org/en/latest/topics/item-pipeline.html#take-screenshot-of-item

JavaScript

一、先试着英汉翻译一波:

1.按F12打开调试台,再点击Network,再点击Headers,可以找到i=good,这就是我们刚才输入需要翻译的词语good,from Data中的就是请求的参数,分别为:

i: good from: AUTO to: AUTO smartresult: dict client: fanyideskweb salt: 15972332870677 sign: 3a078c10344e67f95822ae9389e1363f lts: 1597233287067 bv: 85c050fb1c0b4d824d801d079db7371a doctype: json version: 2.1 keyfrom: fanyi.web action: FY_BY_CLICKBUTTION

2.在来翻译一个新的词语,看下这些参数有无变化

i: 我是中国人 from: AUTO to: AUTO smartresult: dict client: fanyideskweb salt: 15972365771410 sign: 744ebb7fd625f1d4d9d6a270e98536c2 lts: 1597236577141 bv: 85c050fb1c0b4d824d801d079db7371a doctype: json version: 2.1 keyfrom: fanyi.web action: FY_BY_CLICKBUTTION

发现有5个参数是变化的(实际是4 个),分别为:

i: salt: sign: lts: bv:

3.看看URL,#http://fanyi.youdao.com/translate_o?smartresult=dict&smartresult=rule() 请求连接:http://fanyi.youdao.com/translate?smartresult=dict&smartresult=rule 去掉translate_o中的_o,不去的话,轻轻就数据结果:某道词典 ,英语翻译汉语正常,汉语翻译英语时,出现{‘errorCode’: 50} ,post请求方式

4.点击Initiator,可以看到所有的js文件都是@fanyi.min.js:1

5、点击@fanyi.min.js:1进入,

点击中间的{ },格式化一下。

再找到XHR/fetch Breakpoints,添加断点, 你可以针对某一个请求或者请求的关键字设置断点:

再点一下‘+’输入url即可url为http://fanyi.youdao.com/translate_o?smartresult=dict&smartresult=rule

最后再去点击一下翻译按钮

6.翻译:我是中国人,点击t-translate,再点击Scope,再点击Local,再点击r,即可看到我们需要的参数,在吧鼠标放到中间的r上,r = v.generateSaltSign(n),就会弹出Object框。就是我们需要的参数。

7.可以看到8941行,鼠标放到v.gen erateSaltSign(n)上,会弹出f r(e)函数,点击这个函数会进入第8368行,可以看到我们需要的参数

salt: sign: lts: bv:

在8378行打上断点,再次点击翻译按钮,即可看多所有的参数值显示出来。

8.对于第一个 ts: r,相当于pythonzhong 的ts=r赋值, 可以看到r = “” + (new Date).getTime(),知道了r,就知道了ts,r是js中 new Date().getTime()得到的是毫秒数,也就是时间戳,单位为毫秒,13位数字的字符串。

即可用python中的时间戳构造:

13位时间戳获取方法:单位:毫秒 t1 = time.time() ts= int(t1 * 1000) ts=r

第二个参数:salt: i,相当于python中的salt= i赋值,i = r + parseInt(10 * Math.random(), 10),意思是随机产生一个整数 范围是0-9里面的一个随机数

ts=r,转化为字符转,parseInt(10 * Math.random(), 10)产生的随机数也转化为字符串,最后进行字符串拼接,而不是数字相加。请注意一下。跟实际的赋值对比,产生了一个14位数字的字符串,跟实际情况一样。

即可用python中的构造法: salt= str(int(ts))+str(random.randint(0,10)) print(salt)

第三个参数:bv: t,相当于python中的bv=t赋值, var t = n.md5(navigator.appVersion),navigator.appVersion的值竟然是一个 User-Agent,那么重点来了,网上教程都是md5加密相关都是直接用python原生来生成,而我就选择直接扣代码。用python3调用js的库之execjs 来扣,鼠标放到var t = n.md5(navigator.appVersion)中的n.md5()上,将会出现f md5(e)函数,点击进入,来到8196行, md5: function(e){ },把第8196行到8278行扣下来,再应用execjs解析出bv

注意:自调函数调用写法:

var aa=function(e){ }和function aa(e){ }写法都可以,一样的

import execjs f = open(r”text.js”,encoding=’utf-8’).read() ctx1 = execjs.compile(f) bv=ctx1.call(‘md5_1’,ua.random) print(bv)

扣出来的js代码:主体为function md51(e) { } ,应用execjs解析,缺什么参数找什么参数,即可_

var n = function(e, t) { return e << t | e >>> 32 - t } , r = function(e, t) { var n, r, i, a, o; return i = 2147483648 & e, a = 2147483648 & t, n = 1073741824 & e, r = 1073741824 & t, o = (1073741823 & e) + (1073741823 & t), n & r ? 2147483648 ^ o ^ i ^ a : n | r ? 1073741824 & o ? 3221225472 ^ o ^ i ^ a : 1073741824 ^ o ^ i ^ a : o ^ i ^ a } , i = function(e, t, n) { return e & t | ~e & n } , a = function(e, t, n) { return e & n | t & ~n } , o = function(e, t, n) { return e ^ t ^ n } , s = function(e, t, n) { return t ^ (e | ~n) } , l = function(e, t, a, o, s, l, c) { return e = r(e, r(r(i(t, a, o), s), c)), r(n(e, l), t) } , c = function(e, t, i, o, s, l, c) { return e = r(e, r(r(a(t, i, o), s), c)), r(n(e, l), t) } , u = function(e, t, i, a, s, l, c) { return e = r(e, r(r(o(t, i, a), s), c)), r(n(e, l), t) } , d = function(e, t, i, a, o, l, c) { return e = r(e, r(r(s(t, i, a), o), c)), r(n(e, l), t) } , f = function(e) { for (var t, n = e.length, r = n + 8, i = 16 * ((r - r % 64) / 64 + 1), a = Array(i - 1), o = 0, s = 0; s < n; ) o = s % 4 * 8, a[t = (s - s % 4) / 4] = a[t] | e.charCodeAt(s) << o, s++; return t = (s - s % 4) / 4, o = s % 4 * 8, a[t] = a[t] | 128 << o, a[i - 2] = n << 3, a[i - 1] = n >>> 29, a } , p = function(e) { var t, n = "", r = ""; for (t = 0; t <= 3; t++) n += (r = "0" + (e >>> 8 * t & 255).toString(16)).substr(r.length - 2, 2); return n }, h = function(e) { e = e.replace(/\\x0d\\x0a/g, "\\n"); for (var t = "", n = 0; n < e.length; n++) { var r = e.charCodeAt(n); if (r < 128) t += String.fromCharCode(r); else if (r > 127 && r < 2048) t += String.fromCharCode(r >> 6 | 192), t += String.fromCharCode(63 & r | 128); else if (r >= 55296 && r <= 56319) { if (n + 1 < e.length) { var i = e.charCodeAt(n + 1); if (i >= 56320 && i <= 57343) { var a = 1024 * (r - 55296) + (i - 56320) + 65536; t += String.fromCharCode(240 | a >> 18 & 7), t += String.fromCharCode(128 | a >> 12 & 63), t += String.fromCharCode(128 | a >> 6 & 63), t += String.fromCharCode(128 | 63 & a), n++ } } } else t += String.fromCharCode(r >> 12 | 224), t += String.fromCharCode(r >> 6 & 63 | 128), t += String.fromCharCode(63 & r | 128) } return t }; f = function(e) { for (var t, n = e.length, r = n + 8, i = 16 * ((r - r % 64) / 64 + 1), a = Array(i - 1), o = 0, s = 0; s < n; ) o = s % 4 * 8, a[t = (s - s % 4) / 4] = a[t] | e.charCodeAt(s) << o, s++; return t = (s - s % 4) / 4, o = s % 4 * 8, a[t] = a[t] | 128 << o, a[i - 2] = n << 3, a[i - 1] = n >>> 29, a } function md5_1(e) { var t, n, i, a, o, s, m, g, v, y = Array(); for (e = e, y = f, s = 1732584193, m = 4023233417, g = 2562383102, v = 271733878, t = 0; t < y.length; t += 16) n = s, i = m, a = g, o = v, s = l(s, m, g, v, y[t + 0], 7, 3614090360), v = l(v, s, m, g, y[t + 1], 12, 3905402710), g = l(g, v, s, m, y[t + 2], 17, 606105819), m = l(m, g, v, s, y[t + 3], 22, 3250441966), s = l(s, m, g, v, y[t + 4], 7, 4118548399), v = l(v, s, m, g, y[t + 5], 12, 1200080426), g = l(g, v, s, m, y[t + 6], 17, 2821735955), m = l(m, g, v, s, y[t + 7], 22, 4249261313), s = l(s, m, g, v, y[t + 8], 7, 1770035416), v = l(v, s, m, g, y[t + 9], 12, 2336552879), g = l(g, v, s, m, y[t + 10], 17, 4294925233), m = l(m, g, v, s, y[t + 11], 22, 2304563134), s = l(s, m, g, v, y[t + 12], 7, 1804603682), v = l(v, s, m, g, y[t + 13], 12, 4254626195), g = l(g, v, s, m, y[t + 14], 17, 2792965006), m = l(m, g, v, s, y[t + 15], 22, 1236535329), s = c(s, m, g, v, y[t + 1], 5, 4129170786), v = c(v, s, m, g, y[t + 6], 9, 3225465664), g = c(g, v, s, m, y[t + 11], 14, 643717713), m = c(m, g, v, s, y[t + 0], 20, 3921069994), s = c(s, m, g, v, y[t + 5], 5, 3593408605), v = c(v, s, m, g, y[t + 10], 9, 38016083), g = c(g, v, s, m, y[t + 15], 14, 3634488961), m = c(m, g, v, s, y[t + 4], 20, 3889429448), s = c(s, m, g, v, y[t + 9], 5, 568446438), v = c(v, s, m, g, y[t + 14], 9, 3275163606), g = c(g, v, s, m, y[t + 3], 14, 4107603335), m = c(m, g, v, s, y[t + 8], 20, 1163531501), s = c(s, m, g, v, y[t + 13], 5, 2850285829), v = c(v, s, m, g, y[t + 2], 9, 4243563512), g = c(g, v, s, m, y[t + 7], 14, 1735328473), m = c(m, g, v, s, y[t + 12], 20, 2368359562), s = u(s, m, g, v, y[t + 5], 4, 4294588738), v = u(v, s, m, g, y[t + 8], 11, 2272392833), g = u(g, v, s, m, y[t + 11], 16, 1839030562), m = u(m, g, v, s, y[t + 14], 23, 4259657740), s = u(s, m, g, v, y[t + 1], 4, 2763975236), v = u(v, s, m, g, y[t + 4], 11, 1272893353), g = u(g, v, s, m, y[t + 7], 16, 4139469664), m = u(m, g, v, s, y[t + 10], 23, 3200236656), s = u(s, m, g, v, y[t + 13], 4, 681279174), v = u(v, s, m, g, y[t + 0], 11, 3936430074), g = u(g, v, s, m, y[t + 3], 16, 3572445317), m = u(m, g, v, s, y[t + 6], 23, 76029189), s = u(s, m, g, v, y[t + 9], 4, 3654602809), v = u(v, s, m, g, y[t + 12], 11, 3873151461), g = u(g, v, s, m, y[t + 15], 16, 530742520), m = u(m, g, v, s, y[t + 2], 23, 3299628645), s = d(s, m, g, v, y[t + 0], 6, 4096336452), v = d(v, s, m, g, y[t + 7], 10, 1126891415), g = d(g, v, s, m, y[t + 14], 15, 2878612391), m = d(m, g, v, s, y[t + 5], 21, 4237533241), s = d(s, m, g, v, y[t + 12], 6, 1700485571), v = d(v, s, m, g, y[t + 3], 10, 2399980690), g = d(g, v, s, m, y[t + 10], 15, 4293915773), m = d(m, g, v, s, y[t + 1], 21, 2240044497), s = d(s, m, g, v, y[t + 8], 6, 1873313359), v = d(v, s, m, g, y[t + 15], 10, 4264355552), g = d(g, v, s, m, y[t + 6], 15, 2734768916), m = d(m, g, v, s, y[t + 13], 21, 1309151649), s = d(s, m, g, v, y[t + 4], 6, 4149444226), v = d(v, s, m, g, y[t + 11], 10, 3174756917), g = d(g, v, s, m, y[t + 2], 15, 718787259), m = d(m, g, v, s, y[t + 9], 21, 3951481745), s = r(s, n), m = r(m, i), g = r(g, a), v = r(v, o); return (p(s) + p(m) + p(g) + p(v)).toLowerCase() }

第四个参数:sign: n.md5(“fanyideskweb” + e + i + “]BjuETDhU)zqSxf-=B#7m”),相当于python中的sign=n.md5(“fanyideskweb” + e + i + “]BjuETDhU)zqSxf-=B#7m”), salt=i,e是你输入需要翻译的词语,即可写成sign=n.md5(“fanyideskweb” +str(e) + str(salt) + “]BjuETDhU)zqSxf-=B#7m”),继续选择直接扣代码。用python3调用js的库之execjs 来扣,鼠标放到 sign: n.md5(“fanyideskweb” + e + i + “]BjuETDhU)zqSxf-=B#7m”)中的n.md5()上,将会出现f md5(e)函数,点击进入,来到8196行, md5: function(e){ },把第8196行到8278行扣下来,竟然和刚才的扣bv的方法相同,仅仅是参数不同而已,bv的是 n.md5(navigator.appVersion)中的navigator.appVersion参数,现在的asin的是sign=n.md5(“fanyideskweb” +str(e) + str(salt) + “]BjuETDhU)zqSxf-=B#7m”)中的”fanyideskweb” +str(e) + str(salt) + “]BjuETDhU)zqSxf-=B#7m”参数,再应用execjs解析出sign.

注意:自调函数调用写法:

var aa=function(e){ }和function aa(e){ }写法都可以,一样的

import execjs f = open(r”text.js”,encoding=’utf-8’).read() ctx1 = execjs.compile(f) bv=ctx1.call(‘md5_2’,ua.random) print(bv)

扣出来的js代码:主体为function md52(e) { } ,应用execjs解析,缺什么参数找什么参数,即可_

var n = function(e, t) { return e << t | e >>> 32 - t } , r = function(e, t) { var n, r, i, a, o; return i = 2147483648 & e, a = 2147483648 & t, n = 1073741824 & e, r = 1073741824 & t, o = (1073741823 & e) + (1073741823 & t), n & r ? 2147483648 ^ o ^ i ^ a : n | r ? 1073741824 & o ? 3221225472 ^ o ^ i ^ a : 1073741824 ^ o ^ i ^ a : o ^ i ^ a } , i = function(e, t, n) { return e & t | ~e & n } , a = function(e, t, n) { return e & n | t & ~n } , o = function(e, t, n) { return e ^ t ^ n } , s = function(e, t, n) { return t ^ (e | ~n) } , l = function(e, t, a, o, s, l, c) { return e = r(e, r(r(i(t, a, o), s), c)), r(n(e, l), t) } , c = function(e, t, i, o, s, l, c) { return e = r(e, r(r(a(t, i, o), s), c)), r(n(e, l), t) } , u = function(e, t, i, a, s, l, c) { return e = r(e, r(r(o(t, i, a), s), c)), r(n(e, l), t) } , d = function(e, t, i, a, o, l, c) { return e = r(e, r(r(s(t, i, a), o), c)), r(n(e, l), t) } , f = function(e) { for (var t, n = e.length, r = n + 8, i = 16 * ((r - r % 64) / 64 + 1), a = Array(i - 1), o = 0, s = 0; s < n; ) o = s % 4 * 8, a[t = (s - s % 4) / 4] = a[t] | e.charCodeAt(s) << o, s++; return t = (s - s % 4) / 4, o = s % 4 * 8, a[t] = a[t] | 128 << o, a[i - 2] = n << 3, a[i - 1] = n >>> 29, a } , p = function(e) { var t, n = "", r = ""; for (t = 0; t <= 3; t++) n += (r = "0" + (e >>> 8 * t & 255).toString(16)).substr(r.length - 2, 2); return n }, h = function(e) { e = e.replace(/\\x0d\\x0a/g, "\\n"); for (var t = "", n = 0; n < e.length; n++) { var r = e.charCodeAt(n); if (r < 128) t += String.fromCharCode(r); else if (r > 127 && r < 2048) t += String.fromCharCode(r >> 6 | 192), t += String.fromCharCode(63 & r | 128); else if (r >= 55296 && r <= 56319) { if (n + 1 < e.length) { var i = e.charCodeAt(n + 1); if (i >= 56320 && i <= 57343) { var a = 1024 * (r - 55296) + (i - 56320) + 65536; t += String.fromCharCode(240 | a >> 18 & 7), t += String.fromCharCode(128 | a >> 12 & 63), t += String.fromCharCode(128 | a >> 6 & 63), t += String.fromCharCode(128 | 63 & a), n++ } } } else t += String.fromCharCode(r >> 12 | 224), t += String.fromCharCode(r >> 6 & 63 | 128), t += String.fromCharCode(63 & r | 128) } return t }; f = function(e) { for (var t, n = e.length, r = n + 8, i = 16 * ((r - r % 64) / 64 + 1), a = Array(i - 1), o = 0, s = 0; s < n; ) o = s % 4 * 8, a[t = (s - s % 4) / 4] = a[t] | e.charCodeAt(s) << o, s++; return t = (s - s % 4) / 4, o = s % 4 * 8, a[t] = a[t] | 128 << o, a[i - 2] = n << 3, a[i - 1] = n >>> 29, a }

function md5_2(e) { var t, n, i, a, o, s, m, g, v, y = Array(); for (e = e, y = f(e), s = 1732584193, m = 4023233417, g = 2562383102, v = 271733878, t = 0; t < y.length; t += 16) n = s, i = m, a = g, o = v, s = l(s, m, g, v, y[t + 0], 7, 3614090360), v = l(v, s, m, g, y[t + 1], 12, 3905402710), g = l(g, v, s, m, y[t + 2], 17, 606105819), m = l(m, g, v, s, y[t + 3], 22, 3250441966), s = l(s, m, g, v, y[t + 4], 7, 4118548399), v = l(v, s, m, g, y[t + 5], 12, 1200080426), g = l(g, v, s, m, y[t + 6], 17, 2821735955), m = l(m, g, v, s, y[t + 7], 22, 4249261313), s = l(s, m, g, v, y[t + 8], 7, 1770035416), v = l(v, s, m, g, y[t + 9], 12, 2336552879), g = l(g, v, s, m, y[t + 10], 17, 4294925233), m = l(m, g, v, s, y[t + 11], 22, 2304563134), s = l(s, m, g, v, y[t + 12], 7, 1804603682), v = l(v, s, m, g, y[t + 13], 12, 4254626195), g = l(g, v, s, m, y[t + 14], 17, 2792965006), m = l(m, g, v, s, y[t + 15], 22, 1236535329), s = c(s, m, g, v, y[t + 1], 5, 4129170786), v = c(v, s, m, g, y[t + 6], 9, 3225465664), g = c(g, v, s, m, y[t + 11], 14, 643717713), m = c(m, g, v, s, y[t + 0], 20, 3921069994), s = c(s, m, g, v, y[t + 5], 5, 3593408605), v = c(v, s, m, g, y[t + 10], 9, 38016083), g = c(g, v, s, m, y[t + 15], 14, 3634488961), m = c(m, g, v, s, y[t + 4], 20, 3889429448), s = c(s, m, g, v, y[t + 9], 5, 568446438), v = c(v, s, m, g, y[t + 14], 9, 3275163606), g = c(g, v, s, m, y[t + 3], 14, 4107603335), m = c(m, g, v, s, y[t + 8], 20, 1163531501), s = c(s, m, g, v, y[t + 13], 5, 2850285829), v = c(v, s, m, g, y[t + 2], 9, 4243563512), g = c(g, v, s, m, y[t + 7], 14, 1735328473), m = c(m, g, v, s, y[t + 12], 20, 2368359562), s = u(s, m, g, v, y[t + 5], 4, 4294588738), v = u(v, s, m, g, y[t + 8], 11, 2272392833), g = u(g, v, s, m, y[t + 11], 16, 1839030562), m = u(m, g, v, s, y[t + 14], 23, 4259657740), s = u(s, m, g, v, y[t + 1], 4, 2763975236), v = u(v, s, m, g, y[t + 4], 11, 1272893353), g = u(g, v, s, m, y[t + 7], 16, 4139469664), m = u(m, g, v, s, y[t + 10], 23, 3200236656), s = u(s, m, g, v, y[t + 13], 4, 681279174), v = u(v, s, m, g, y[t + 0], 11, 3936430074), g = u(g, v, s, m, y[t + 3], 16, 3572445317), m = u(m, g, v, s, y[t + 6], 23, 76029189), s = u(s, m, g, v, y[t + 9], 4, 3654602809), v = u(v, s, m, g, y[t + 12], 11, 3873151461), g = u(g, v, s, m, y[t + 15], 16, 530742520), m = u(m, g, v, s, y[t + 2], 23, 3299628645), s = d(s, m, g, v, y[t + 0], 6, 4096336452), v = d(v, s, m, g, y[t + 7], 10, 1126891415), g = d(g, v, s, m, y[t + 14], 15, 2878612391), m = d(m, g, v, s, y[t + 5], 21, 4237533241), s = d(s, m, g, v, y[t + 12], 6, 1700485571), v = d(v, s, m, g, y[t + 3], 10, 2399980690), g = d(g, v, s, m, y[t + 10], 15, 4293915773), m = d(m, g, v, s, y[t + 1], 21, 2240044497), s = d(s, m, g, v, y[t + 8], 6, 1873313359), v = d(v, s, m, g, y[t + 15], 10, 4264355552), g = d(g, v, s, m, y[t + 6], 15, 2734768916), m = d(m, g, v, s, y[t + 13], 21, 1309151649), s = d(s, m, g, v, y[t + 4], 6, 4149444226), v = d(v, s, m, g, y[t + 11], 10, 3174756917), g = d(g, v, s, m, y[t + 2], 15, 718787259), m = d(m, g, v, s, y[t + 9], 21, 3951481745), s = r(s, n), m = r(m, i), g = r(g, a), v = r(v, o); return (p(s) + p(m) + p(g) + p(v)).toLowerCase() }

9.大功告成,四个可变参数全部搞定,即可搞定&

附上完整代码 python部分:

import time import execjs import random import requests import json from fake_useragent import UserAgent # var aa=function(e) function aa(e)

class YouDaoTanslate(): def init(self): #类方法调用 self.js_par() def js_par(self): translation_data = input(“请输入要翻译的词或者句子:”) # 13位时间戳获取方法:单位:毫秒 ua = UserAgent() print(ua.random) t1 = time.time() ts = int(t1 * 1000) print(ts) salt = str(int(ts)) + str(random.randint(0, 10)) print(salt)

f = open(r”text.js”, encoding=’utf-8’).read() ctx1 = execjs.compile(f) bv = ctx1.call(‘md5_1’, ua.random) print(bv)

ctx2 = execjs.compile(f) sign_data = ‘fanyideskweb’ + translation_data + salt + ‘mmbP%A-r6U3Nw(n]BjuEU’ sign = ctx2.call(‘md5_2’, sign_data) print(sign)

self.get_translateData(translation_data,ua,ts,salt,bv,sign) def get_translateData(self,translation_data,ua,ts,salt,bv,sign): url = ‘http://fanyi.youdao.com/translate?smartresult=dict&smartresult=rule‘ headers = { ‘Host’: ‘fanyi.youdao.com’, ‘Origin’: ‘http://fanyi.youdao.com‘, ‘Referer’: ‘http://fanyi.youdao.com/‘, ‘User-Agent’: ua.random, ‘X-Requested-With’: ‘XMLHttpRequest’, ‘Cookie’: ‘OUTFOX_SEARCH_USER_ID_NCOO=892433278.3204294; OUTFOX_SEARCH_USER_ID=”-1911793285@10.108.160.19”; _ntes_nnid=d2bb7793f13c9a83907e33d40665337a,1597158692753; JSESSIONID=aaamUZK4O580YaKjmjIpx; _rltest__cookies=1597227802599’ } data = { ‘i’: translation_data, ‘from’: ‘AUTO’, ‘to’: ‘AUTO’, ‘smartresult’: ‘dict’, ‘client’: ‘fanyideskweb’, ‘salt’: str(salt), ‘sign’: str(sign), ‘ts’: str(ts), ‘bv’: str(bv), ‘doctype’: ‘json’, ‘version’: ‘2.1’, ‘keyfrom’: ‘fanyi.web’, ‘action’: ‘FY_BY_CLICKBUTTION’ }

response = requests.post(url=url, data=data, headers=headers)

response = requests.post(url=url, data=data, headers=headers) translate_results = json.loads(response.text) # 找到翻译结果 if ‘translateResult’ in translate_results: translate_results = translate_results[‘translateResult’][0][0][‘tgt’] print(“翻译的结果是:%s” % translate_results)

else: print(translate_results)

self.js_par()

YouDaoTanslate()

js部分:

var n = function(e, t) { return e << t | e >>> 32 - t } , r = function(e, t) { var n, r, i, a, o; return i = 2147483648 & e, a = 2147483648 & t, n = 1073741824 & e, r = 1073741824 & t, o = (1073741823 & e) + (1073741823 & t), n & r ? 2147483648 ^ o ^ i ^ a : n | r ? 1073741824 & o ? 3221225472 ^ o ^ i ^ a : 1073741824 ^ o ^ i ^ a : o ^ i ^ a } , i = function(e, t, n) { return e & t | ~e & n } , a = function(e, t, n) { return e & n | t & ~n } , o = function(e, t, n) { return e ^ t ^ n } , s = function(e, t, n) { return t ^ (e | ~n) } , l = function(e, t, a, o, s, l, c) { return e = r(e, r(r(i(t, a, o), s), c)), r(n(e, l), t) } , c = function(e, t, i, o, s, l, c) { return e = r(e, r(r(a(t, i, o), s), c)), r(n(e, l), t) } , u = function(e, t, i, a, s, l, c) { return e = r(e, r(r(o(t, i, a), s), c)), r(n(e, l), t) } , d = function(e, t, i, a, o, l, c) { return e = r(e, r(r(s(t, i, a), o), c)), r(n(e, l), t) } , f = function(e) { for (var t, n = e.length, r = n + 8, i = 16 * ((r - r % 64) / 64 + 1), a = Array(i - 1), o = 0, s = 0; s < n; ) o = s % 4 * 8, a[t = (s - s % 4) / 4] = a[t] | e.charCodeAt(s) << o, s++; return t = (s - s % 4) / 4, o = s % 4 * 8, a[t] = a[t] | 128 << o, a[i - 2] = n << 3, a[i - 1] = n >>> 29, a } , p = function(e) { var t, n = "", r = ""; for (t = 0; t <= 3; t++) n += (r = "0" + (e >>> 8 * t & 255).toString(16)).substr(r.length - 2, 2); return n }, h = function(e) { e = e.replace(/\\x0d\\x0a/g, "\\n"); for (var t = "", n = 0; n < e.length; n++) { var r = e.charCodeAt(n); if (r < 128) t += String.fromCharCode(r); else if (r > 127 && r < 2048) t += String.fromCharCode(r >> 6 | 192), t += String.fromCharCode(63 & r | 128); else if (r >= 55296 && r <= 56319) { if (n + 1 < e.length) { var i = e.charCodeAt(n + 1); if (i >= 56320 && i <= 57343) { var a = 1024 * (r - 55296) + (i - 56320) + 65536; t += String.fromCharCode(240 | a >> 18 & 7), t += String.fromCharCode(128 | a >> 12 & 63), t += String.fromCharCode(128 | a >> 6 & 63), t += String.fromCharCode(128 | 63 & a), n++ } } } else t += String.fromCharCode(r >> 12 | 224), t += String.fromCharCode(r >> 6 & 63 | 128), t += String.fromCharCode(63 & r | 128) } return t }; f = function(e) { for (var t, n = e.length, r = n + 8, i = 16 * ((r - r % 64) / 64 + 1), a = Array(i - 1), o = 0, s = 0; s < n; ) o = s % 4 * 8, a[t = (s - s % 4) / 4] = a[t] | e.charCodeAt(s) << o, s++; return t = (s - s % 4) / 4, o = s % 4 * 8, a[t] = a[t] | 128 << o, a[i - 2] = n << 3, a[i - 1] = n >>> 29, a } function md5_1(e) { var t, n, i, a, o, s, m, g, v, y = Array(); for (e = e, y = f, s = 1732584193, m = 4023233417, g = 2562383102, v = 271733878, t = 0; t < y.length; t += 16) n = s, i = m, a = g, o = v, s = l(s, m, g, v, y[t + 0], 7, 3614090360), v = l(v, s, m, g, y[t + 1], 12, 3905402710), g = l(g, v, s, m, y[t + 2], 17, 606105819), m = l(m, g, v, s, y[t + 3], 22, 3250441966), s = l(s, m, g, v, y[t + 4], 7, 4118548399), v = l(v, s, m, g, y[t + 5], 12, 1200080426), g = l(g, v, s, m, y[t + 6], 17, 2821735955), m = l(m, g, v, s, y[t + 7], 22, 4249261313), s = l(s, m, g, v, y[t + 8], 7, 1770035416), v = l(v, s, m, g, y[t + 9], 12, 2336552879), g = l(g, v, s, m, y[t + 10], 17, 4294925233), m = l(m, g, v, s, y[t + 11], 22, 2304563134), s = l(s, m, g, v, y[t + 12], 7, 1804603682), v = l(v, s, m, g, y[t + 13], 12, 4254626195), g = l(g, v, s, m, y[t + 14], 17, 2792965006), m = l(m, g, v, s, y[t + 15], 22, 1236535329), s = c(s, m, g, v, y[t + 1], 5, 4129170786), v = c(v, s, m, g, y[t + 6], 9, 3225465664), g = c(g, v, s, m, y[t + 11], 14, 643717713), m = c(m, g, v, s, y[t + 0], 20, 3921069994), s = c(s, m, g, v, y[t + 5], 5, 3593408605), v = c(v, s, m, g, y[t + 10], 9, 38016083), g = c(g, v, s, m, y[t + 15], 14, 3634488961), m = c(m, g, v, s, y[t + 4], 20, 3889429448), s = c(s, m, g, v, y[t + 9], 5, 568446438), v = c(v, s, m, g, y[t + 14], 9, 3275163606), g = c(g, v, s, m, y[t + 3], 14, 4107603335), m = c(m, g, v, s, y[t + 8], 20, 1163531501), s = c(s, m, g, v, y[t + 13], 5, 2850285829), v = c(v, s, m, g, y[t + 2], 9, 4243563512), g = c(g, v, s, m, y[t + 7], 14, 1735328473), m = c(m, g, v, s, y[t + 12], 20, 2368359562), s = u(s, m, g, v, y[t + 5], 4, 4294588738), v = u(v, s, m, g, y[t + 8], 11, 2272392833), g = u(g, v, s, m, y[t + 11], 16, 1839030562), m = u(m, g, v, s, y[t + 14], 23, 4259657740), s = u(s, m, g, v, y[t + 1], 4, 2763975236), v = u(v, s, m, g, y[t + 4], 11, 1272893353), g = u(g, v, s, m, y[t + 7], 16, 4139469664), m = u(m, g, v, s, y[t + 10], 23, 3200236656), s = u(s, m, g, v, y[t + 13], 4, 681279174), v = u(v, s, m, g, y[t + 0], 11, 3936430074), g = u(g, v, s, m, y[t + 3], 16, 3572445317), m = u(m, g, v, s, y[t + 6], 23, 76029189), s = u(s, m, g, v, y[t + 9], 4, 3654602809), v = u(v, s, m, g, y[t + 12], 11, 3873151461), g = u(g, v, s, m, y[t + 15], 16, 530742520), m = u(m, g, v, s, y[t + 2], 23, 3299628645), s = d(s, m, g, v, y[t + 0], 6, 4096336452), v = d(v, s, m, g, y[t + 7], 10, 1126891415), g = d(g, v, s, m, y[t + 14], 15, 2878612391), m = d(m, g, v, s, y[t + 5], 21, 4237533241), s = d(s, m, g, v, y[t + 12], 6, 1700485571), v = d(v, s, m, g, y[t + 3], 10, 2399980690), g = d(g, v, s, m, y[t + 10], 15, 4293915773), m = d(m, g, v, s, y[t + 1], 21, 2240044497), s = d(s, m, g, v, y[t + 8], 6, 1873313359), v = d(v, s, m, g, y[t + 15], 10, 4264355552), g = d(g, v, s, m, y[t + 6], 15, 2734768916), m = d(m, g, v, s, y[t + 13], 21, 1309151649), s = d(s, m, g, v, y[t + 4], 6, 4149444226), v = d(v, s, m, g, y[t + 11], 10, 3174756917), g = d(g, v, s, m, y[t + 2], 15, 718787259), m = d(m, g, v, s, y[t + 9], 21, 3951481745), s = r(s, n), m = r(m, i), g = r(g, a), v = r(v, o); return (p(s) + p(m) + p(g) + p(v)).toLowerCase() }

var n = function(e, t) { return e << t | e >>> 32 - t } , r = function(e, t) { var n, r, i, a, o; return i = 2147483648 & e, a = 2147483648 & t, n = 1073741824 & e, r = 1073741824 & t, o = (1073741823 & e) + (1073741823 & t), n & r ? 2147483648 ^ o ^ i ^ a : n | r ? 1073741824 & o ? 3221225472 ^ o ^ i ^ a : 1073741824 ^ o ^ i ^ a : o ^ i ^ a } , i = function(e, t, n) { return e & t | ~e & n } , a = function(e, t, n) { return e & n | t & ~n } , o = function(e, t, n) { return e ^ t ^ n } , s = function(e, t, n) { return t ^ (e | ~n) } , l = function(e, t, a, o, s, l, c) { return e = r(e, r(r(i(t, a, o), s), c)), r(n(e, l), t) } , c = function(e, t, i, o, s, l, c) { return e = r(e, r(r(a(t, i, o), s), c)), r(n(e, l), t) } , u = function(e, t, i, a, s, l, c) { return e = r(e, r(r(o(t, i, a), s), c)), r(n(e, l), t) } , d = function(e, t, i, a, o, l, c) { return e = r(e, r(r(s(t, i, a), o), c)), r(n(e, l), t) } , f = function(e) { for (var t, n = e.length, r = n + 8, i = 16 ((r - r % 64) / 64 + 1), a = Array(i - 1), o = 0, s = 0; s < n; ) o = s % 4 8, a[t = (s - s % 4) / 4] = a[t] | e.charCodeAt(s) << o, s++; return t = (s - s % 4) / 4, o = s % 4 8, a[t] = a[t] | 128 << o, a[i - 2] = n << 3, a[i - 1] = n >>> 29, a } , p = function(e) { var t, n = “”, r = “”; for (t = 0; t <= 3; t++) n += (r = “0” + (e >>> 8 t & 255).toString(16)).substr(r.length - 2, 2); return n }, h = function(e) { e = e.replace(/\x0d\x0a/g, “\n”); for (var t = “”, n = 0; n < e.length; n++) { var r = e.charCodeAt(n); if (r < 128) t += String.fromCharCode(r); else if (r > 127 && r < 2048) t += String.fromCharCode(r >> 6 | 192), t += String.fromCharCode(63 & r | 128); else if (r >= 55296 && r <= 56319) { if (n + 1 < e.length) { var i = e.charCodeAt(n + 1); if (i >= 56320 && i <= 57343) { var a = 1024 (r - 55296) + (i - 56320) + 65536; t += String.fromCharCode(240 | a >> 18 & 7), t += String.fromCharCode(128 | a >> 12 & 63), t += String.fromCharCode(128 | a >> 6 & 63), t += String.fromCharCode(128 | 63 & a), n++ } } } else t += String.fromCharCode(r >> 12 | 224), t += String.fromCharCode(r >> 6 & 63 | 128), t += String.fromCharCode(63 & r | 128) } return t }; f = function(e) { for (var t, n = e.length, r = n + 8, i = 16 ((r - r % 64) / 64 + 1), a = Array(i - 1), o = 0, s = 0; s < n; ) o = s % 4 8, a[t = (s - s % 4) / 4] = a[t] | e.charCodeAt(s) << o, s++; return t = (s - s % 4) / 4, o = s % 4 8, a[t] = a[t] | 128 << o, a[i - 2] = n << 3, a[i - 1] = n >>> 29, a }

function md5_2(e) { var t, n, i, a, o, s, m, g, v, y = Array(); for (e = e, y = f(e), s = 1732584193, m = 4023233417, g = 2562383102, v = 271733878, t = 0; t < y.length; t += 16) n = s, i = m, a = g, o = v, s = l(s, m, g, v, y[t + 0], 7, 3614090360), v = l(v, s, m, g, y[t + 1], 12, 3905402710), g = l(g, v, s, m, y[t + 2], 17, 606105819), m = l(m, g, v, s, y[t + 3], 22, 3250441966), s = l(s, m, g, v, y[t + 4], 7, 4118548399), v = l(v, s, m, g, y[t + 5], 12, 1200080426), g = l(g, v, s, m, y[t + 6], 17, 2821735955), m = l(m, g, v, s, y[t + 7], 22, 4249261313), s = l(s, m, g, v, y[t + 8], 7, 1770035416), v = l(v, s, m, g, y[t + 9], 12, 2336552879), g = l(g, v, s, m, y[t + 10], 17, 4294925233), m = l(m, g, v, s, y[t + 11], 22, 2304563134), s = l(s, m, g, v, y[t + 12], 7, 1804603682), v = l(v, s, m, g, y[t + 13], 12, 4254626195), g = l(g, v, s, m, y[t + 14], 17, 2792965006), m = l(m, g, v, s, y[t + 15], 22, 1236535329), s = c(s, m, g, v, y[t + 1], 5, 4129170786), v = c(v, s, m, g, y[t + 6], 9, 3225465664), g = c(g, v, s, m, y[t + 11], 14, 643717713), m = c(m, g, v, s, y[t + 0], 20, 3921069994), s = c(s, m, g, v, y[t + 5], 5, 3593408605), v = c(v, s, m, g, y[t + 10], 9, 38016083), g = c(g, v, s, m, y[t + 15], 14, 3634488961), m = c(m, g, v, s, y[t + 4], 20, 3889429448), s = c(s, m, g, v, y[t + 9], 5, 568446438), v = c(v, s, m, g, y[t + 14], 9, 3275163606), g = c(g, v, s, m, y[t + 3], 14, 4107603335), m = c(m, g, v, s, y[t + 8], 20, 1163531501), s = c(s, m, g, v, y[t + 13], 5, 2850285829), v = c(v, s, m, g, y[t + 2], 9, 4243563512), g = c(g, v, s, m, y[t + 7], 14, 1735328473), m = c(m, g, v, s, y[t + 12], 20, 2368359562), s = u(s, m, g, v, y[t + 5], 4, 4294588738), v = u(v, s, m, g, y[t + 8], 11, 2272392833), g = u(g, v, s, m, y[t + 11], 16, 1839030562), m = u(m, g, v, s, y[t + 14], 23, 4259657740), s = u(s, m, g, v, y[t + 1], 4, 2763975236), v = u(v, s, m, g, y[t + 4], 11, 1272893353), g = u(g, v, s, m, y[t + 7], 16, 4139469664), m = u(m, g, v, s, y[t + 10], 23, 3200236656), s = u(s, m, g, v, y[t + 13], 4, 681279174), v = u(v, s, m, g, y[t + 0], 11, 3936430074), g = u(g, v, s, m, y[t + 3], 16, 3572445317), m = u(m, g, v, s, y[t + 6], 23, 76029189), s = u(s, m, g, v, y[t + 9], 4, 3654602809), v = u(v, s, m, g, y[t + 12], 11, 3873151461), g = u(g, v, s, m, y[t + 15], 16, 530742520), m = u(m, g, v, s, y[t + 2], 23, 3299628645), s = d(s, m, g, v, y[t + 0], 6, 4096336452), v = d(v, s, m, g, y[t + 7], 10, 1126891415), g = d(g, v, s, m, y[t + 14], 15, 2878612391), m = d(m, g, v, s, y[t + 5], 21, 4237533241), s = d(s, m, g, v, y[t + 12], 6, 1700485571), v = d(v, s, m, g, y[t + 3], 10, 2399980690), g = d(g, v, s, m, y[t + 10], 15, 4293915773), m = d(m, g, v, s, y[t + 1], 21, 2240044497), s = d(s, m, g, v, y[t + 8], 6, 1873313359), v = d(v, s, m, g, y[t + 15], 10, 4264355552), g = d(g, v, s, m, y[t + 6], 15, 2734768916), m = d(m, g, v, s, y[t + 13], 21, 1309151649), s = d(s, m, g, v, y[t + 4], 6, 4149444226), v = d(v, s, m, g, y[t + 11], 10, 3174756917), g = d(g, v, s, m, y[t + 2], 15, 718787259), m = d(m, g, v, s, y[t + 9], 21, 3951481745), s = r(s, n), m = r(m, i), g = r(g, a), v = r(v, o); return (p(s) + p(m) + p(g) + p(v)).toLowerCase() }