来源: jQuery字母大小写转换函数 – 柚子茶丶 – 博客园
toLowerCase() ------ 将字符串中的所有字符都转换成小写;toUpperCase() ------ 将字符串中的所有字符都转换成大写;来源: jQuery字母大小写转换函数 – 柚子茶丶 – 博客园
toLowerCase() ------ 将字符串中的所有字符都转换成小写;toUpperCase() ------ 将字符串中的所有字符都转换成大写;点赞再看,养成习惯,微信搜一搜【三太子敖丙】关注这个互联网苟且偷生的工具人。
本文 GitHub https://github.com/JavaFamily 已收录,有一线大厂面试完整考点、资料以及我的系列文章。
我之前写过一个秒杀系统的文章不过有些许瑕疵,所以我准备在之前的基础上进行二次创作,不过让我决心二创秒杀系统的原因是我最近面试了很多读者,动不动就是秒杀系统把我整蒙蔽了,我懵的主要是秒杀系统的细节大家都不知道,甚至不知道电商公司一个秒杀系统的组成部分。
我之前在某电商公司就是做电商活动的,所以这样的场景和很多解决方案我是比较清楚的,那我就从我自身去带着大家看看一个秒杀的设计细节以及中间各种解决方案的利弊,以下就是我设计的秒杀系统,几乎涵盖了市面上所有秒杀的实现细节:

首先设计一个系统之前,我们需要先确认我们的业务场景是怎么样子的,我就带着大家一起假设一个场景好吧。
我们现场要卖1000件下面这个婴儿纸尿裤,然后我们根据以往这样秒杀活动的数据经验来看,目测来抢这100件纸尿裤的人足足有10万人。(南极人打钱!)

你一听,完了呀,这我们的服务器哪里顶得住啊!说真的直接打DB肯定挂,但是别急嘛,有暖男敖丙在,任何系统我们开始设计之前我们都应该去思考会出现哪些问题?这里我罗列了几个非常经典的问题:
是的高并发这个是我们想都不用想的一个点,一瞬间这么多人进来这不是高并发什么时候是呢?
是吧,秒杀的特点就是这样时间极短、 瞬间用户量大。
正常的店铺营销都是用极低的价格配合上短信、APP的精准推送,吸引特别多的用户来参与这场秒杀,爽了商家苦了开发呀。
秒杀大家都知道如果真的营销到位,价格诱人,几十万的流量我觉得完全不是问题,那单机的Redis我感觉3-4W的QPS还是能顶得住的,但是再高了就没办法了,那这个数据随便搞个热销商品的秒杀可能都不止了。
大量的请求进来,我们需要考虑的点就很多了,缓存雪崩,缓存击穿,缓存穿透这些我之前提到的点都是有可能发生的,出现问题打挂DB那就很难受了,活动失败用户体验差,活动人气没了,最后背锅的还是开发。

但凡是个秒杀,都怕超卖,我这里举例的只是尿不湿,要是换成100个MacBook Pro,商家的预算经费卖100个可以赚点还可以造势,结果你写错程序多卖出去200个,你不发货用户投诉你,平台封你店,你发货就血亏,你怎么办? (没事看了敖丙的文章直接不怕)
那最后只能杀个开发祭天解气了,秒杀的价格本来就低了,基本上都是不怎么赚钱的,超卖了就恐怖了呀,所以超卖也是很关键的一个点。

你这么低的价格,假如我抢到了,我转手卖掉我不是血赚?就算我不卖我也不亏啊,那用户知道,你知道,别的别有用心的人(黑客、黄牛…)肯定也知道的。
那简单啊,我知道你什么时候抢,我搞个几十台机器搞点脚本,我也模拟出来十几万个人左右的请求,那我是不是意味着我基本上有80%的成功率了。
真实情况可能远远不止,因为机器请求的速度比人的手速往往快太多了,在贵州的敖丙我每年回家抢高铁票都是秒光的,我也不知道有没有黄牛的功劳,我要Diss你,黄牛。杰伦演唱会门票抢不到,我也Diss你。
Tip:科普下,小道消息了解到的,黄牛的抢票系统,比国内很多小公司的系统还吊很多,架构设计都是顶级的,我用顶配的服务加上顶配的架构设计,你还想看演唱会?还想回家?
不过不用黄牛我回家都难,我们云贵川跟我一样要回家过年的仔太多了555!
前面几个问题大家可能都很好理解,一看到这个有的小伙伴可能会比较疑惑,啥是链接暴露呀?

相信是个开发同学都对这个画面一点都不陌生吧,懂点行的仔都可以打开谷歌的开发者模式,然后看看你的网页代码,有的就有URL,但是我写VUE的时候是事件触发然后去调用文件里面的接口看源码看不到,但是我可以点击一下查看你的请求地址啊,不过你好像可以对按钮在秒杀前置灰。
不管怎么样子都有危险,撇开外面的所有的东西你都挡住了,你卖这个东西实在便宜得过分,有诱惑力,你能保证开发不动心?开发知道地址,在秒杀的时候自己提前请求。。。(开发:怎么TM又是我)

每秒上万甚至十几万的QPS(每秒请求数)直接打到数据库,基本上都要把库打挂掉,而且你服务不单单是做秒杀的还涉及其他的业务,你没做降级、限流、熔断啥的,别的一起挂,小公司的话可能全站崩溃404。
反正不管你秒杀怎么挂,你别把别的搞挂了对吧,搞挂了就不是杀一个程序员能搞定的。
程序员:我TM好难啊!
问题都列出来了,那怎么设计,怎么解决这些问题就是接下去要考虑的了,我们对症下药。
我会从我设计的秒杀系统从上到下去给大家介绍我们正常电商秒杀系统在每一层做了些什么,每一层存在的问题,难点等。
我们从前端开始:
秒杀系统普遍都是商城网页、H5、APP、小程序这几项。
在前端这一层其实我们可以做的事情有很多,如果用node去做,甚至能直接处理掉整个秒杀,但是node其实应该属于后端,所以我不讨论node Service了。
秒杀一般都是特定的商品还有页面模板,现在一般都是前后端分离的,页面一般都是不会经过后端的,但是前端也要自己的服务器啊,那就把能提前放入cdn服务器的东西都放进去,反正把所有能提升效率的步骤都做一下,减少真正秒杀时候服务器的压力。
我们上面说了链接要是提前暴露出去可能有人直接访问url就提前秒杀了,那又有小伙伴要说了我做个时间的校验就好了呀,那我告诉你,知道链接的地址比起页面人工点击的还是有很大优势。
我知道url了,那我通过程序不断获取最新的北京时间,可以达到毫秒级别的,我就在00毫秒的时候请求,我敢说绝对比你人工点的成功率大太多了,而且我可以一毫秒发送N次请求,搞不好你卖100个产品我全拿了。

那这种情况怎么避免?
简单,把URL动态化,就连写代码的人都不知道,你就通过MD5之类的摘要算法加密随机的字符串去做url,然后通过前端代码获取url后台校验才能通过。
这个只能防止一部分没耐心继续破解下去的黑客,有耐心的人研究出来还是能破解,在电商场景存在很多这样的羊毛党,那怎么做呢?
后面我会说。
限流这里我觉得应该分为前端限流和后端限流。
物理控制:
大家有没有发现没到秒杀前,一般按钮都是置灰的,只有时间到了,才能点击。
这是因为怕大家在时间快到的最后几秒秒疯狂请求服务器,然后还没到秒杀的时候基本上服务器就挂了。
这个时候就需要前端的配合,定时去请求你的后端服务器,获取最新的北京时间,到时间点再给按钮可用状态。
按钮可以点击之后也得给他置灰几秒,不然他一样在开始之后一直点的。
你敢说你们秒杀的时候不是这样的?

前端限流:这个很简单,一般秒杀不会让你一直点的,一般都是点击一下或者两下然后几秒之后才可以继续点击,这也是保护服务器的一种手段。
后端限流:秒杀的时候肯定是涉及到后续的订单生成和支付等操作,但是都只是成功的幸运儿才会走到那一步,那一旦100个产品卖光了,return了一个false,前端直接秒杀结束,然后你后端也关闭后续无效请求的介入了。
Tip:真正的限流还会有限流组件的加入例如:阿里的Sentinel、Hystrix等。我这里就不展开了,就说一下物理的限流。
我们卖1000件商品,请求有10W,我们不需要把十万都放进来,你可以放1W请求进来,然后再进行操作,因为秒杀对于用户本身就是黑盒的,所以你怎么做的他们是没感知的,至于为啥放1W进来,而不是刚好1000,是因为会丢掉一些薅羊毛的用户,至于怎么判断,后面的风控阶段我会说。
Nginx大家想必都不陌生了吧,这玩意是高性能的web服务器,并发也随便顶几万不是梦,但是我们的Tomcat只能顶几百的并发呀,那简单呀负载均衡嘛,一台服务几百,那就多搞点,在秒杀的时候多租点流量机。
Tip:据我所知国内某大厂就是在去年春节活动期间租光了亚洲所有的服务器,小公司也很喜欢在双十一期间买流量机来顶住压力。

这样一对比是不是觉得你的集群能顶很多了。
恶意请求拦截也需要用到它,一般单个用户请求次数太夸张,不像人为的请求在网关那一层就得拦截掉了,不然请求多了他抢不抢得到是一回事,服务器压力上去了,可能占用网络带宽或者把服务器打崩、缓存击穿等等。
我可以明确的告诉大家,前面的所有措施还是拦不住很多羊毛党,因为他们是专业的团队,他们可以注册很多账号来薅你的羊毛,而且不用机器请求,就用群控,操作几乎跟真实用户一模一样。

那怎么办,是不是无解了?
这个时候就需要风控同学的介入了,在请求到达后端之前,风控可以根据账号行为分析出这个账号机器人的概率大不大,我现在负责公司的某些特殊系统,每个用户的行为都是会送到我们大数据团队进行分析处理,给你打上对应标签的。
那黑客其实也有办法:养号
他们去黑市买真实用户有过很多记录的账号,买到了还不闲着,帮他们去购物啥的,让系统无法识别他们是黑号还是真实用户的号。
怎么办?
通杀!是的没有办法,只能通杀了,通杀的意思就是,我们通过风管分析出来这个用户是真实用户的概率没有其他用户概率大,那就认为他是机器了,丢弃他的请求。
之前的限流我们放进来10000个请求,但是我们真正的库存只有1000个,那我们就算出最有可能是真实用户的1000人进行秒杀,丢弃其他请求,因为秒杀本来就是黑盒操作的,用户层面是无感知的,这样设计能让真实的用户买到东西,还可以减少自己被薅羊毛的概率。
风控可以说是流量进入的最后一道门槛了,所以很多公司的风控是很强的,蚂蚁金服的风控大家如果了解过就知道了,你的资金在支付宝被盗了,他们是能做到全款补偿是有原因的。
设计个能抗住高并发的系统,我觉得还是得单一职责。
什么意思呢,大家都知道现在设计都是微服务的设计思想,然后再用分布式的部署方式。
也就是我们下单是有个订单服务,用户登录管理等有个用户服务等等,那为啥我们不给秒杀也开个服务,我们把秒杀的代码业务逻辑放一起。
单一职责的好处就是就算秒杀没抗住,秒杀库崩了,服务挂了,也不会影响到其他的服务。(高可用)
之前不是说单机的Redis顶不住嘛,那简单多找几个兄弟啊,秒杀本来就是读多写少,那你们是不是瞬间想起来我之前跟你们提到过的,Redis集群,主从同步、读写分离,我们还搞点哨兵,开启持久化直接无敌高可用!

秒杀的本质,就是对库存的抢夺,每个秒杀的用户来你都去数据库查询库存校验库存,然后扣减库存,撇开性能因数,你不觉得这样好繁琐,对业务开发人员都不友好,而且数据库顶不住啊。
开发:你tm总算为我着想一次了。

我们都知道数据库顶不住但是他的兄弟非关系型的数据库Redis能顶啊!
那不简单了,我们要开始秒杀前你通过定时任务或者运维同学提前把商品的库存加载到Redis中去,让整个流程都在Redis里面去做,然后等秒杀介绍了,再异步的去修改库存就好了。
但是用了Redis就有一个问题了,我们上面说了我们采用主从,就是我们会去读取库存然后再判断然后有库存才去减库存,正常情况没问题,但是高并发的情况问题就很大了。
**多品几遍!!!**就比如现在库存只剩下1个了,我们高并发嘛,4个服务器一起查询了发现都是还有1个,那大家都觉得是自己抢到了,就都去扣库存,那结果就变成了-3,是的只有一个是真的抢到了,别的都是超卖的。咋办?
Redis本身是支持事务的,而且他有很多原子命令的,大家也可以用LUA,还可以用他的管道,乐观锁他也知支持。
这个为啥要做呢,不怕一万就怕万一,万一你真的顶不住了,限流,顶不住就挡一部分出去但是不能说不行,降级,降级了还是被打挂了,熔断,至少不要影响别的系统,隔离,你本身就独立的,但是你会调用其他的系统嘛,你快不行了你别拖累兄弟们啊。

一说到这个名词,很多小伙伴就知道了,对的MQ,你买东西少了你直接100个请求改库我觉得没问题,但是万一秒杀一万个,10万个呢?服务器挂了,程序员又要背锅的。
秒杀就是这种瞬间流量很高,但是平时又没有流量的场景,那消息队列完全契合这样的场景了呀,削峰填谷。

Tip:可能小伙伴说我们业务达不到这个量级,没必要。但是我想说我们写代码,就不应该写出有逻辑漏洞的代码,至少以后公司体量上去了,别人一看居然不用改代码,一看代码作者是敖丙?有点东西!
你可以把它放消息队列,然后一点点消费去改库存就好了嘛,不过单个商品其实一次修改就够了,我这里说的是某个点多个商品一起秒杀的场景,像极了双十一零点。
数据库用MySQL只要连接池设置合理一般问题是不大的,不过一般大公司不缺钱而且秒杀这样的活动十分频繁,我之前所在的公司就是这样秒杀特卖这样的场景一直都是不间断的。
单独给秒杀建立一个数据库,为秒杀服务,表的设计也是竟可能的简单点,现在的互联网架构部署都是分库的。
至于表就看大家怎么设计了,该设置索引的地方还是要设置索引的,建完后记得用explain看看SQL的执行计划。(不了解的小伙伴也没事,MySQL章节去康康)
这为啥我不放在后端而放到最后来讲呢?
因为上面的任何一步都是可能出错的,而且我们是在不同的服务里面出错的,那就涉及分布式事务了,但是分布式事务大家想的是一定要成功什么的那就不对了,还是那句话,几个请求丢了就丢了,要保证时效和服务的可用可靠。
所以TCC和最终一致性其实不是很适合,TCC开发成本很大,所有接口都要写三次,因为涉及TCC的三个阶段。
最终一致性基本上都是靠轮训的操作去保证一个操作一定成功,那时效性就大打折扣了。
大家觉得不那么可靠的**两段式(2PC)和三段式(3PC)**就派上用场了,他们不一定能保证数据最终一致,但是效率上还算ok。
到这里我想我已经基本上把该考虑的点还有对应的解决方案也都说了一下,不知道还有没有没考虑到的,但是就算没考虑到我想我这个设计,应该也能撑住一个完整的秒杀流程。
最后大家再看看这个秒杀系统或许会有新的感悟,是不是一个系统真的没有大家想的那么简单,而且我还是有漏掉的细节,这是一定的。

秒杀这章我脑细胞死了很多,考虑了很多个点,最后还是出来了,忍不住给自己点赞!
我们玩归玩,闹归闹,别拿面试开玩笑。
秒杀不一定是每个同学都会问到的,至少肯定没Redis基础那样常问,但是一旦问到,大家一定要回答到点上。
至少你得说出可能出现的情况,需要注意的情况,以及对于的解决思路和方案,因为这才是一个coder的基本素养,这些你不考虑你也很难去进步。
最后就是需要对整个链路比较熟悉,注意是一个完整的链路,前端怎么设计的呀,网关的作用呀,怎么解决Redis的并发竞争啊,数据的同步方式呀,MQ的作用啊等等,相信你会有不错的收获。
不知道这是一次成功还是失败的二创,我里面所有提到的技术细节我都写了对应的文章,大家可以关注我去历史文章看看,天色已晚,我溜了。
另外,敖丙把自己的面试文章整理成了一本电子书,共 1630页!目录如下,还有我复习时总结的面试题以及简历模板

return false
来源: jquery的$.each如何退出循环和退出本次循环 – 咖啡无眠 – 博客园
JQuery中each类似于JavaScript的for循环
但不同于for循环的是在each里面不能使用break结束循环,也不能使用continue来结束本次循环,想要实现类似的功能就只能用return,
break 用return false
continue 用return true
经常会看到这样似错非错的提示:
当前上下文中不存在名称”__o”
The name ‘__o’ does not exist in the current context

实际上,我没有定义任何名为 __o 的变量。
发生这种情况的原因可能是使用了类似如下的代码:
|
1
2
3
4
|
<% if(true) { %><%= 1 %><% } %><%= 2 %> |
为了在设计界面的 <%= %> 代码块中提供智能感知,ASP.NET(VB 或 C#)会自动生成一个名为“__o”的临时变量,这在页面编译器看到第一个 <%= %> 块时就完成了。但是在这里,<%= %> 块在 if 中出现,所以当关闭 if 后再使用 <%= %> 时,变量超出了定义的范围。
|
1
2
3
4
5
6
|
if (true){ object @__o; @__o = 1;}@__o = 2; |
解决方法:在页面的早期添加一个虚表达式。例如:<%= “” %>。这将不会呈现任何内容,并且它将确保在任何潜在的 if(或其他范围界定)语句之前,在 Render 方法中将 __o 声明为顶级。
当然还有一种治标不治本的方法就是隐藏这些错误提示(这并不影响程序正常运行):
点击错误列表面板左上角的过滤器按钮,CS0103,其中包含错误代码:当前上下文中不存在名称”__o”,这些错误将不再显示,您仍然可以有其他 IntelliSense 错误和警告。
有趣的依赖问题…… VS2015.3,.NET45将Microsoft.Owin从3.0.0更新到3.0.1(跟踪此更新是问题 – 想要安装https://www.nuget.org/packages/IdentityServer3.AccessTokenValidation/) 它在IIS Express上本地工作 但发布到Azure网站:(我直接从VS删除目标上的其他文件).重新启动并删除了a
来源: c# – 无法将文件或程序集Microsoft.Owin 3.0.0加载到3.0.1 – 程序园
但发布到Azure网站:(我直接从VS删除目标上的其他文件).重新启动并删除了azure webserver中的所有文件.
web.config翻译看起来没问题
<dependentAssembly>
<assemblyIdentity name="Microsoft.Owin" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0" />
</dependentAssembly>
编辑
Bruce Chen的回答指出了我的方向Kuud(Azure – 开发工具,高级工具)
我下载了Owin dll,这是错误的版本,即3.0.0
为了解决这个问题,我清除了解决方案中的所有包工件
git clean -xfd(小心)
然后重建
Could not load file or assembly ‘Microsoft.Owin, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35’ or one of its dependencies. The located assembly’s manifest definition does not match the assembly reference.
通常,这意味着.NET程序集加载程序无法找到具有引用的特定版本的程序集.请确保您的Azure网站中可以找到版本3.0.1.0的程序集,并且该版本通过KUDU或FTP客户端与web.config中的定义匹配.
正如您所说,它在IIS Express上本地工作,请尝试通过KUDU或FTP客户端手动将您的网站内容部署到Azure,并找出它是否有效.
来源: SQL Server 取日期时间 – 一技赢 – 博客园
在本文中,GetDate()获得的日期由两部分组成,分别是今天的日期和当时的时间: Select GetDate()
用DateName()就可以获得相应的年、月、日,然后再把它们连接起来就可以了:
Select Datename(year,GetDate())+’-‘+Datename
(month,GetDate())+’-‘+Datename(day,GetDate())
另外,DateName()还可以获得到小时、时间、秒、星期几、第几周,分别如下:
Select Datename(hour,GetDate())
Select Datename(minute,GetDate())
Select Datename(second,GetDate())
Select Datename(weekDay,GetDate())
Select Datename(week,GetDate())
SQL中的日期类型DateTime的默认格式就是yyyy-mm-dd hh:mi:ss: mmm,可大多数的情况我们只想得到他的日期部分,而不许要后面的时间。上一篇中提到用Datename()函数来截取拼接出不包含时间部分的日期,现在再说一种方法,更加简单的获取到不包含时间的日期!
使用Convert()函数:
select convert(char(10),GetDate(),120) as Date
* 第3个参数就是用来设置日期类型数据的显示样式的,下面介绍几种样式的参数:
100 mm dd yyyy
101 mm/dd/yyyy
102 yyyy.mm.dd
103 dd/mm/yyyy
106 dd mm yyyy
108 hh:mi:ss(时间)
111 yyyy/mm/dd
112 yyyymmdd
120 yyyy-mm-dd
1 SELECT CONVERT(varchar(100), GETDATE(), 0) 05 9 2011 9:12AM 2 SELECT CONVERT(varchar(100), GETDATE(), 1) 05/09/11 3 SELECT CONVERT(varchar(100), GETDATE(), 2) 11.05.09 4 SELECT CONVERT(varchar(100), GETDATE(), 3) 09/05/11 5 SELECT CONVERT(varchar(100), GETDATE(), 4) 09.05.11 6 SELECT CONVERT(varchar(100), GETDATE(), 5) 09-05-11 7 SELECT CONVERT(varchar(100), GETDATE(), 6) 09 05 11 8 SELECT CONVERT(varchar(100), GETDATE(), 7) 05 09, 11 9 SELECT CONVERT(varchar(100), GETDATE(), 8) 09:13:14 10 SELECT CONVERT(varchar(100), GETDATE(), 9) 05 9 2011 9:13:14:670AM 11 SELECT CONVERT(varchar(100), GETDATE(), 10) 05-09-11 12 SELECT CONVERT(varchar(100), GETDATE(), 11) 11/05/09 13 SELECT CONVERT(varchar(100), GETDATE(), 12) 110509 14 SELECT CONVERT(varchar(100), GETDATE(), 13) 09 05 2011 09:13:14:670 15 SELECT CONVERT(varchar(100), GETDATE(), 14) 09:13:14:670 16 SELECT CONVERT(varchar(100), GETDATE(), 20) 2011-05-09 09:13:14 17 SELECT CONVERT(varchar(100), GETDATE(), 21) 2011-05-09 09:13:14.670 18 SELECT CONVERT(varchar(100), GETDATE(), 22) 05/09/11 9:15:33 AM 19 SELECT CONVERT(varchar(100), GETDATE(), 23) 2011-05-09 20 SELECT CONVERT(varchar(100), GETDATE(), 24) 09:15:33 21 SELECT CONVERT(varchar(100), GETDATE(), 25) 2011-05-09 09:15:33.140 22 SELECT CONVERT(varchar(100), GETDATE(), 100) 05 9 2011 9:15AM 23 SELECT CONVERT(varchar(100), GETDATE(), 101) 05/09/2011 24 SELECT CONVERT(varchar(100), GETDATE(), 102) 2011.05.09 25 SELECT CONVERT(varchar(100), GETDATE(), 103) 09/05/2011 26 SELECT CONVERT(varchar(100), GETDATE(), 104) 09.05.2011 27 SELECT CONVERT(varchar(100), GETDATE(), 105) 09-05-2011 28 SELECT CONVERT(varchar(100), GETDATE(), 106) 09 05 2011 29 SELECT CONVERT(varchar(100), GETDATE(), 107) 05 09, 2011 30 SELECT CONVERT(varchar(100), GETDATE(), 108) 09:16:38 31 SELECT CONVERT(varchar(100), GETDATE(), 109) 05 9 2011 9:16:38:543AM 32 SELECT CONVERT(varchar(100), GETDATE(), 110) 05-09-2011 33 SELECT CONVERT(varchar(100), GETDATE(), 111) 2011/05/09 34 SELECT CONVERT(varchar(100), GETDATE(), 112) 20110509 35 SELECT CONVERT(varchar(100), GETDATE(), 113) 09 05 2011 09:17:19:857 36 SELECT CONVERT(varchar(100), GETDATE(), 114) 09:17:19:857 37 SELECT CONVERT(varchar(100), GETDATE(), 120) 2011-05-09 09:17:19 38 SELECT CONVERT(varchar(100), GETDATE(), 121) 2011-05-09 09:17:19.857 39 SELECT CONVERT(varchar(100), GETDATE(), 126) 2011-05-09T09:17:19.857 40 SELECT CONVERT(varchar(100), GETDATE(), 130) 6 ????? ??????? 1432 9:17:19:857AM 41 SELECT CONVERT(varchar(100), GETDATE(), 131) 6/06/1432 9:17:19:857AM
来源: 用Python爬取股票数据,绘制K线和均线并用机器学习预测股价(来自我出的书) – hsm_computer – 博客园
最近我出了一本书,《基于股票大数据分析的Python入门实战 视频教学版》,京东链接:https://item.jd.com/69241653952.html,在其中用股票范例讲述Python爬虫、数据分析和机器学习的技术,大家看了我的书,不仅能很快用比较热门的案例学好Python,更能了解些股票知识,不至于一入市就拍脑袋买卖。
在本文里,将给出若干精彩范例,包括用爬虫获取股市数据,用matplotlib可视化控件绘制K线和均线,以及用sklean库里的方法,通过机器学习预测股价的走势。
pandas_datareader是一个能读取各种金融数据的库,在下面的getDataByPandasDatareader.py范例程序中演示了通过这个库获取股市数据的常规方法。
1 # coding=utf-8
2 from pandas_datareader import data as pdr
3 import yfinance as yf
4 yf.pdr_override()
5 code='600895.ss'
6 stock = pdr.get_data_yahoo(code,'2019-01-02','2019-02-01')
7 print(stock) # 输出内容
8 # 保存为excel和csv文件
9 stock.to_excel('D:\\stockData\\ch5\\'+code+'.xlsx')
10 stock.to_csv('D:\\stockData\ch5\\'+code+'.csv')
从这个范例程序的代码上来看,不算复杂,从中没有见到爬取网站之类的代码。关键的是第6行,通过调用pdr.get_data_yahoo方法从雅虎网站获取数据,这个方法的参数分别是股票代码,开始日期和结束日期。第4行使用yf.pdr_override方法是为了防止雅虎网站修改获取历史数据的API接口而导致get_data_yahoo方法不可用。
在这个范例程序中获取了600895(张江高科)2019-01-02到2019-01-31的数据,可以看出,获取的数据并不包括结束日期参数当天的数据。
在第7行和第8行分别调用了to_excel和to_csv方法,把结果存入了指定目录下的文件中。这个范例程序运行后,我们首先能在控制台中看到输出,其次会在D:\stockData\ch5\目录中,看到600895.ss.xlsx和600895.ss.csv这两个保存股票数据的文件。打开600895.ss.xlsx文件,能看到如图5-4所示的数据内容,其实在控制台中和另一个csv文件中,可以看到一样的数据。

在上述范例程序中,在调用get_data_yahoo方法时,传入的股票代码带有.ss的后缀,这表示该代码是沪股的。此外,还能通过.sz的后缀来表示深股,通过.hk的后缀表示港股。如果要获取美股的数据,则直接用美股的股票代码即可。在下面的printDataByPandasDatareader.py范例程序中演示了获取美股,港股和深股相关数据的方式。
1 # coding=utf-8
2 from pandas_datareader import data as pdr
3 import yfinance as yf
4 yf.pdr_override()
5 stockCodeList = []
6 stockCodeList.append('600007.ss') # 沪股“中国国贸”
7 stockCodeList.append('000001.sz') # 深股“平安银行”
8 stockCodeList.append('2318.hk') # 港股“中国平安”
9 stockCodeList.append('IBM') # 美股,IBM,直接输入股票代码不带后缀
10 for code in stockCodeList:
11 # 为了演示,只取一天(2019-01-02)的交易数据
12 stock = pandas_datareader.get_data_yahoo(code,'2019-01-02','2019-01-03')
13 print(stock)
这个范例程序运行后,就能从控制台中看到输出的4个股票在指定日期内的交易情况,由于数据量比较多,本书就不罗列具体的数据了。
K线是由开盘价、收盘价、最高价和最低价这四个要素构成。在得到上述四个值之后,首先用开盘价和收盘价绘制成一个长方形实体。随后根据最高价和最低价,把它们垂直地同长方形实体连成一条直线,这条直线就叫影线。如果再细分一下,长方形实体上方的就叫上影线,下方的就叫下影线。通过K线可以形象地记录价格变动的情况,常用的有日K线,周K线和月K线。
均线也叫移动平均线(Moving Average,简称MA),是指某段时间内的平均股价(或指数)连成的曲线,均线一般分为三类:短期、中期和长期。通常把5日和10日移动平均线称为短期均线,一般把20日、30日和60日移动平均线作为中期均线,一般120日和250日(甚至更长)移动平均线称为长期均线。
在如下的drawKAndMAMore.py范例程序中,将用到上文提到的爬取股票数据的代码,从网络接口里获取股票数据,并绘制k线和均线,请大家不仅注意k线和均线的含义,还要重视matplotlib库里绘制图形、图例和坐标轴的做法,在这本书里,对应的知识点都有详细的说明。
1 # !/usr/bin/env python
2 # coding=utf-8
3 from pandas_datareader import data as pdr
4 import pandas as pd
5 import matplotlib.pyplot as plt
6 from mpl_finance import candlestick2_ochl
7 from matplotlib.ticker import MultipleLocator
8 import yfinance as yf
9 yf.pdr_override()
10 # 根据指定代码和时间范围获取股票数据
11 code='600895.ss'
12 stock.drop(stock.index[len(stock)-1],inplace=True)
13 # 保存在本地
14 stock.to_csv('D:\\stockData\ch7\\600895.csv')
15 df = pd.read_csv('D:/stockData/ch7/600895.csv',encoding='gbk',index_col=0)
16 # 设置窗口大小
17 fig, ax = plt.subplots(figsize=(10, 8))
18 xmajorLocator = MultipleLocator(5) # 将x轴主刻度设置为5的倍数
19 ax.xaxis.set_major_locator(xmajorLocator)
20 # 调用方法绘制K线图
21 candlestick2_ochl(ax = ax, opens=df["Open"].values,closes=df["Close"].values, highs=df["High"].values, lows=df["Low"].values,width=0.75, colorup='red', colordown='green')
22 # 如下是绘制3种均线
23 df['Close'].rolling(window=3).mean().plot(color="red",label='3日均线')
24 df['Close'].rolling(window=5).mean().plot(color="blue",label='5日均线')
25 df['Close'].rolling(window=10).mean().plot(color="green",label='10日均线')
26 plt.legend(loc='best') # 绘制图例
27 ax.grid(True) # 带网格线
28 plt.title("600895张江高科的K线图")
29 plt.rcParams['font.sans-serif']=['SimHei']
30 plt.setp(plt.gca().get_xticklabels(), rotation=30)
31 plt.show()
第一,从第9行到第15行通过调用之前介绍过的get_data_yahoo方法,传入股票代码、开始时间和结束时间这三个参数,从雅虎网站中获得股票交易的数据。
第二,在第17行中调用figsize方法设置了窗口的大小。
第三,第18行和第19行的程序代码设置了主刻度是5的倍数。之所以设置成5的倍数,是因为一般一周的交易日是5天。但这里不能简单地把主刻度设置成每周一,因为某些周一有可能是股市休市的法定假日。
第四,由于无需在x轴上设置每天的日期,因此这里无需再调用plt.xticks方法,但是要调用如第30行所示的代码,设置x轴刻度的旋转角度,否则x轴显示的时间依然有可能会相互重叠。
至于绘制K线的candlestick2_ochl方法和绘制均线的rolling方法与之前drawKAndMA.py范例程序中的代码是完全一致的。
这个范例程序的运行结果如图7-5所示,从中可以看到改进后的效果。由于本次显示的股票时间段变长了(是3个月),因此与drawKAndMA.py范例程序相比,这个范例程序均线的效果更为明显,尤其是3日均线,几乎贯穿于整个时间段的各个交易日。
另外,由于在第26行通过调用plt.legend(loc=’best’)方法指定了图例将“显示在合适的位置”,因此这里的图例显示在效果更加合适的左上方,而不是drawKAndMA.py范例程序中的右上方。

在下面的predictStockByLR.py范例程序中,根据股票历史的开盘价、收盘价和成交量等特征值,从数学角度来预测股票未来的收盘价。
1 # !/usr/bin/env python
2 # coding=utf-8
3 import pandas as pd
4 import numpy as np
5 import math
6 import matplotlib.pyplot as plt
7 from sklearn.linear_model import LinearRegression
8 from sklearn.model_selection import train_test_split
9 # 从文件中获取数据
10 origDf = pd.read_csv('D:/stockData/ch13/6035052018-09-012019-06-01.csv',encoding='gbk')
11 df = origDf[['Close', 'High', 'Low','Open' ,'Volume']]
12 featureData = df[['Open', 'High', 'Volume','Low']]
13 # 划分特征值和目标值
14 feature = featureData.values
15 target = np.array(df['Close'])
第10行的程序语句从包含股票信息的csv文件中读取数据,在第14行设置了特征值是开盘价、最高价、最低价和成交量,同时在第15行设置了要预测的目标列是收盘价。在后续的代码中,需要将计算出开盘价、最高价、最低价和成交量这四个特征值和收盘价的线性关系,并在此基础上预测收盘价。
16 # 划分训练集,测试集 17 feature_train, feature_test, target_train ,target_test = train_test_split(feature,target,test_size=0.05) 18 pridectedDays = int(math.ceil(0.05 * len(origDf))) # 预测天数 19 lrTool = LinearRegression() 20 lrTool.fit(feature_train,target_train) # 训练 21 # 用测试集预测结果 22 predictByTest = lrTool.predict(feature_test)
第17行的程序语句通过调用train_test_split方法把包含在csv文件中的股票数据分成训练集和测试集,这个方法前两个参数分别是特征列和目标列,而第三个参数0.05则表示测试集的大小是总量的0.05。该方法返回的四个参数分别是特征值的训练集、特征值的测试集、要预测目标列的训练集和目标列的测试集。
第18行的程序语句计算了要预测的交易日数,在第19行中构建了一个线性回归预测的对象,在第20行是调用fit方法训练特征值和目标值的线性关系,请注意这里的训练是针对训练集的,在第22行中,则是用特征值的测试集来预测目标值(即收盘价)。也就是说,是用多个交易日的股价来训练lrTool对象,并在此基础上预测后续交易日的收盘价。至此,上面的程序代码完成了相关的计算工作。
23 # 组装数据 24 index=0 25 # 在前95%的交易日中,设置预测结果和收盘价一致 26 while index < len(origDf) - pridectedDays: 27 df.ix[index,'predictedVal']=origDf.ix[index,'Close'] 28 df.ix[index,'Date']=origDf.ix[index,'Date'] 29 index = index+1 30 predictedCnt=0 31 # 在后5%的交易日中,用测试集推算预测股价 32 while predictedCnt<pridectedDays: 33 df.ix[index,'predictedVal']=predictByTest[predictedCnt] 34 df.ix[index,'Date']=origDf.ix[index,'Date'] 35 predictedCnt=predictedCnt+1 36 index=index+1
在第26行到第29行的while循环中,在第27行把训练集部分的预测股价设置成收盘价,并在第28行设置了训练集部分的日期。
在第32行到第36行的while循环中,遍历了测试集,在第33行的程序语句把df中表示测试结果的predictedVal列设置成相应的预测结果,同时也在第34行的程序语句逐行设置了每条记录中的日期。
37 plt.figure() 38 df['predictedVal'].plot(color="red",label='predicted Data') 39 df['Close'].plot(color="blue",label='Real Data') 40 plt.legend(loc='best') # 绘制图例 41 # 设置x坐标的标签 42 major_index=df.index[df.index%10==0] 43 major_xtics=df['Date'][df.index%10==0] 44 plt.xticks(major_index,major_xtics) 45 plt.setp(plt.gca().get_xticklabels(), rotation=30) 46 # 带网格线,且设置了网格样式 47 plt.grid(linestyle='-.') 48 plt.show()
在完成数据计算和数据组装的工作后,从第37行到第48行程序代码的最后,实现了可视化。
第38行和第39行的程序代码分别绘制了预测股价和真实收盘价,在绘制的时候设置了不同的颜色,也设置了不同的label标签值,在第40行通过调用legend方法,根据收盘价和预测股价的标签值,绘制了相应的图例。
从第42行到第45行设置了x轴显示的标签文字是日期,为了不让标签文字显示过密,设置了“每10个日期里只显示1个”的显示方式,并且在第47行设置了网格线的效果,最后在第48行通过调用show方法绘制出整个图形。运行本范例程序,即可看到如图13-7所示的结果。

可以看出,蓝线表示真实的收盘价(图中完整的线),红线表示预测股价(图中靠右边的线。因为本书黑白印刷的原因,在书中读者看不到蓝色和红色,请读者在自己的计算机上运行这个范例程序即可看到红蓝两色的线)。虽然预测股价和真实价之间有差距,但涨跌的趋势大致相同。而且在预测时没有考虑到涨跌停的因素,所以预测结果的涨跌幅度比真实数据要大。
股票价格不仅由技术面决定,还受政策面、资金量以及消息面等诸多因素的影响,这也能解释预测结果和真实结果间有差异的原因。
本文给出的范例,仅是《基于股票大数据分析的Python入门实战 视频教学版》一书里的部分案例,该书京东链接:https://item.jd.com/69241653952.html。

这本书包括如下的内容,是本入门python的工具书。
1 Python基本语法,集合,面向对象语法,异常处理,读写文件技能。
2 Python操作数据库的技能。
3 通过爬虫从网络接口爬取股票数据的技能。
4 基于Numpy+Pandas+Matplotlib进行数据分析的技能
5 基于TKinter的GUI编程技能+ 发送邮件的技能
6 Django框架的用法
7 线性回归+SVM的机器学习技能
这本书里,像本文那样花花绿绿能吸引人的图真不少,而且还是通过python绘制出来的,用这类比较能吸引人的案例来入门python,一定非常高效。
本文可以转载,转载时请全文转载,别有删节,并用链接的形式给出原文链接。否则的话,可能会遇到出版社的维权。
文本相关链接:
来源: .Net微服务实战之Kubernetes的搭建与使用 – 陈珙 – 博客园
说到微服务就得扯到自动化运维,然后别人就不得不问你用没用上K8S。无论是概念上还是在实施搭建时,K8S的门槛比Docker Compose、Docker Swarm高了不少。我自己也经过了多次的实践,整理出一套顺利部署的流程。
我这次搭建花了一共整整4个工作实践与一个工作日写博客,中间有一个网络问题导致reset了集群重新搭了一次,完成后结合了Jenkins使用,还是成就感满满的。如果对大家有用,还请点个推荐于关注。
kubectl用于运行Kubernetes集群命令的管理工具,Kubernetes kubectl 与 Docker 命令关系可以查看这里
http://docs.kubernetes.org.cn/70.html
kubeadm 是 kubernetes 的集群安装工具,能够快速安装 kubernetes 集群,相关命令有以下:
kubeadm init kubeadm join
kubelet是主要的节点代理,它会监视已分配给节点的pod,具体功能:
Pod是Kubernetes创建或部署的最小(最简单)的基本单位,一个Pod代表集群上正在运行的一个进程,它可能由单个容器或多个容器共享组成的资源。
一个Pod封装一个应用容器(也可以有多个容器),存储资源、一个独立的网络IP以及管理控制容器运行方式的策略选项。
Pods提供两种共享资源:网络和存储。
网络
每个Pod被分配一个独立的IP地址,Pod中的每个容器共享网络命名空间,包括IP地址和网络端口。Pod内的容器可以使用localhost相互通信。当Pod中的容器与Pod 外部通信时,他们必须协调如何使用共享网络资源(如端口)。
存储
Pod可以指定一组共享存储volumes。Pod中的所有容器都可以访问共享volumes,允许这些容器共享数据。volumes 还用于Pod中的数据持久化,以防其中一个容器需要重新启动而丢失数据。
一个应用服务在Kubernetes中可能会有一个或多个Pod,每个Pod的IP地址由网络组件动态随机分配(Pod重启后IP地址会改变)。为屏蔽这些后端实例的动态变化和对多实例的负载均衡,引入了Service这个资源对象。
Kubernetes ServiceTypes 允许指定一个需要的类型的 Service,默认是 ClusterIP 类型。
Type 的取值以及行为如下:
其他详细的概念请移步到 http://docs.kubernetes.org.cn/227.html

在所有需要用到kubernetes服务器上安装docker-ce
卸载旧版本 docker
yum remove docker docker-common docker-selinux dockesr-engine -y
yum upgrade -y
sudo yum install -y yum-utils device-mapper-persistent-data lvm2
yum-config-manager --add-repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
yum makecache fast yum install docker-ce-19.03.12 -y
vim /etc/docker/daemon.json
{
"exec-opts": ["native.cgroupdriver=systemd"],
"registry-mirrors" : [
"http://ovfftd6p.mirror.aliyuncs.com",
"http://registry.docker-cn.com",
"http://docker.mirrors.ustc.edu.cn",
"http://hub-mirror.c.163.com"
],
"insecure-registries" : [
"registry.docker-cn.com",
"docker.mirrors.ustc.edu.cn"
],
"debug" : true,
"experimental" : true
}
启动服务
systemctl start docker systemctl enable docker

所有需要用到kubernetes的服务器都执行以下指令。
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
yum install kubeadm-1.18.3 kubectl-1.18.3 kubelet-1.18.3
启动kubelet
systemctl enable kubelet systemctl start kubelet

vim /etc/profile
export KUBECONFIG=/etc/kubernetes/admin.conf
执行命令使其起效
source /etc/profile
在master节点(server-a)进行初始化集群
开放端口
firewall-cmd --permanent --zone=public --add-port=6443/tcp firewall-cmd --permanent --zone=public --add-port=10250/tcp firewall-cmd --reload
vim /etc/fstab #注释swap那行 swapoff -a
设置iptables规则
echo 1 > /proc/sys/net/bridge/bridge-nf-call-iptables echo 1 > /proc/sys/net/bridge/bridge-nf-call-ip6tables
初始化
kubeadm init --kubernetes-version=1.18.3 --apiserver-advertise-address=192.168.88.138 --image-repository registry.aliyuncs.com/google_containers --service-cidr=10.10.0.0/16 --pod-network-cidr=10.122.0.0/16 --ignore-preflight-errors=Swap
pod-network-cidr参数的为pod网段:,apiserver-advertise-address参数为本机IP。

mkdir -p $HOME/.kube sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config sudo chown $(id -u):$(id -g) $HOME/.kube/config
kubectl get node kubectl get pod --all-namespaces

在master节点(server-a)安装flannel组件
找个梯子下载kube-flannel.yml文件
https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml
下载不了也没关系,我复制给到大家:
---
apiVersion: policy/v1beta1
kind: PodSecurityPolicy
metadata:
name: psp.flannel.unprivileged
annotations:
seccomp.security.alpha.kubernetes.io/allowedProfileNames: docker/default
seccomp.security.alpha.kubernetes.io/defaultProfileName: docker/default
apparmor.security.beta.kubernetes.io/allowedProfileNames: runtime/default
apparmor.security.beta.kubernetes.io/defaultProfileName: runtime/default
spec:
privileged: false
volumes:
- configMap
- secret
- emptyDir
- hostPath
allowedHostPaths:
- pathPrefix: "/etc/cni/net.d"
- pathPrefix: "/etc/kube-flannel"
- pathPrefix: "/run/flannel"
readOnlyRootFilesystem: false
# Users and groups
runAsUser:
rule: RunAsAny
supplementalGroups:
rule: RunAsAny
fsGroup:
rule: RunAsAny
# Privilege Escalation
allowPrivilegeEscalation: false
defaultAllowPrivilegeEscalation: false
# Capabilities
allowedCapabilities: ['NET_ADMIN', 'NET_RAW']
defaultAddCapabilities: []
requiredDropCapabilities: []
# Host namespaces
hostPID: false
hostIPC: false
hostNetwork: true
hostPorts:
- min: 0
max: 65535
# SELinux
seLinux:
# SELinux is unused in CaaSP
rule: 'RunAsAny'
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
name: flannel
rules:
- apiGroups: ['extensions']
resources: ['podsecuritypolicies']
verbs: ['use']
resourceNames: ['psp.flannel.unprivileged']
- apiGroups:
- ""
resources:
- pods
verbs:
- get
- apiGroups:
- ""
resources:
- nodes
verbs:
- list
- watch
- apiGroups:
- ""
resources:
- nodes/status
verbs:
- patch
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
name: flannel
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: flannel
subjects:
- kind: ServiceAccount
name: flannel
namespace: kube-system
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: flannel
namespace: kube-system
---
kind: ConfigMap
apiVersion: v1
metadata:
name: kube-flannel-cfg
namespace: kube-system
labels:
tier: node
app: flannel
data:
cni-conf.json: |
{
"name": "cbr0",
"cniVersion": "0.3.1",
"plugins": [
{
"type": "flannel",
"delegate": {
"hairpinMode": true,
"isDefaultGateway": true
}
},
{
"type": "portmap",
"capabilities": {
"portMappings": true
}
}
]
}
net-conf.json: |
{
"Network": "10.244.0.0/16",
"Backend": {
"Type": "vxlan"
}
}
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: kube-flannel-ds-amd64
namespace: kube-system
labels:
tier: node
app: flannel
spec:
selector:
matchLabels:
app: flannel
template:
metadata:
labels:
tier: node
app: flannel
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/os
operator: In
values:
- linux
- key: kubernetes.io/arch
operator: In
values:
- amd64
hostNetwork: true
priorityClassName: system-node-critical
tolerations:
- operator: Exists
effect: NoSchedule
serviceAccountName: flannel
initContainers:
- name: install-cni
image: quay.io/coreos/flannel:v0.12.0-amd64
command:
- cp
args:
- -f
- /etc/kube-flannel/cni-conf.json
- /etc/cni/net.d/10-flannel.conflist
volumeMounts:
- name: cni
mountPath: /etc/cni/net.d
- name: flannel-cfg
mountPath: /etc/kube-flannel/
containers:
- name: kube-flannel
image: quay.io/coreos/flannel:v0.12.0-amd64
command:
- /opt/bin/flanneld
args:
- --ip-masq
- --kube-subnet-mgr
resources:
requests:
cpu: "100m"
memory: "50Mi"
limits:
cpu: "100m"
memory: "50Mi"
securityContext:
privileged: false
capabilities:
add: ["NET_ADMIN", "NET_RAW"]
env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
volumeMounts:
- name: run
mountPath: /run/flannel
- name: flannel-cfg
mountPath: /etc/kube-flannel/
volumes:
- name: run
hostPath:
path: /run/flannel
- name: cni
hostPath:
path: /etc/cni/net.d
- name: flannel-cfg
configMap:
name: kube-flannel-cfg
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: kube-flannel-ds-arm64
namespace: kube-system
labels:
tier: node
app: flannel
spec:
selector:
matchLabels:
app: flannel
template:
metadata:
labels:
tier: node
app: flannel
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/os
operator: In
values:
- linux
- key: kubernetes.io/arch
operator: In
values:
- arm64
hostNetwork: true
priorityClassName: system-node-critical
tolerations:
- operator: Exists
effect: NoSchedule
serviceAccountName: flannel
initContainers:
- name: install-cni
image: quay.io/coreos/flannel:v0.12.0-arm64
command:
- cp
args:
- -f
- /etc/kube-flannel/cni-conf.json
- /etc/cni/net.d/10-flannel.conflist
volumeMounts:
- name: cni
mountPath: /etc/cni/net.d
- name: flannel-cfg
mountPath: /etc/kube-flannel/
containers:
- name: kube-flannel
image: quay.io/coreos/flannel:v0.12.0-arm64
command:
- /opt/bin/flanneld
args:
- --ip-masq
- --kube-subnet-mgr
resources:
requests:
cpu: "100m"
memory: "50Mi"
limits:
cpu: "100m"
memory: "50Mi"
securityContext:
privileged: false
capabilities:
add: ["NET_ADMIN", "NET_RAW"]
env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
volumeMounts:
- name: run
mountPath: /run/flannel
- name: flannel-cfg
mountPath: /etc/kube-flannel/
volumes:
- name: run
hostPath:
path: /run/flannel
- name: cni
hostPath:
path: /etc/cni/net.d
- name: flannel-cfg
configMap:
name: kube-flannel-cfg
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: kube-flannel-ds-arm
namespace: kube-system
labels:
tier: node
app: flannel
spec:
selector:
matchLabels:
app: flannel
template:
metadata:
labels:
tier: node
app: flannel
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/os
operator: In
values:
- linux
- key: kubernetes.io/arch
operator: In
values:
- arm
hostNetwork: true
priorityClassName: system-node-critical
tolerations:
- operator: Exists
effect: NoSchedule
serviceAccountName: flannel
initContainers:
- name: install-cni
image: quay.io/coreos/flannel:v0.12.0-arm
command:
- cp
args:
- -f
- /etc/kube-flannel/cni-conf.json
- /etc/cni/net.d/10-flannel.conflist
volumeMounts:
- name: cni
mountPath: /etc/cni/net.d
- name: flannel-cfg
mountPath: /etc/kube-flannel/
containers:
- name: kube-flannel
image: quay.io/coreos/flannel:v0.12.0-arm
command:
- /opt/bin/flanneld
args:
- --ip-masq
- --kube-subnet-mgr
resources:
requests:
cpu: "100m"
memory: "50Mi"
limits:
cpu: "100m"
memory: "50Mi"
securityContext:
privileged: false
capabilities:
add: ["NET_ADMIN", "NET_RAW"]
env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
volumeMounts:
- name: run
mountPath: /run/flannel
- name: flannel-cfg
mountPath: /etc/kube-flannel/
volumes:
- name: run
hostPath:
path: /run/flannel
- name: cni
hostPath:
path: /etc/cni/net.d
- name: flannel-cfg
configMap:
name: kube-flannel-cfg
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: kube-flannel-ds-ppc64le
namespace: kube-system
labels:
tier: node
app: flannel
spec:
selector:
matchLabels:
app: flannel
template:
metadata:
labels:
tier: node
app: flannel
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/os
operator: In
values:
- linux
- key: kubernetes.io/arch
operator: In
values:
- ppc64le
hostNetwork: true
priorityClassName: system-node-critical
tolerations:
- operator: Exists
effect: NoSchedule
serviceAccountName: flannel
initContainers:
- name: install-cni
image: quay.io/coreos/flannel:v0.12.0-ppc64le
command:
- cp
args:
- -f
- /etc/kube-flannel/cni-conf.json
- /etc/cni/net.d/10-flannel.conflist
volumeMounts:
- name: cni
mountPath: /etc/cni/net.d
- name: flannel-cfg
mountPath: /etc/kube-flannel/
containers:
- name: kube-flannel
image: quay.io/coreos/flannel:v0.12.0-ppc64le
command:
- /opt/bin/flanneld
args:
- --ip-masq
- --kube-subnet-mgr
resources:
requests:
cpu: "100m"
memory: "50Mi"
limits:
cpu: "100m"
memory: "50Mi"
securityContext:
privileged: false
capabilities:
add: ["NET_ADMIN", "NET_RAW"]
env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
volumeMounts:
- name: run
mountPath: /run/flannel
- name: flannel-cfg
mountPath: /etc/kube-flannel/
volumes:
- name: run
hostPath:
path: /run/flannel
- name: cni
hostPath:
path: /etc/cni/net.d
- name: flannel-cfg
configMap:
name: kube-flannel-cfg
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: kube-flannel-ds-s390x
namespace: kube-system
labels:
tier: node
app: flannel
spec:
selector:
matchLabels:
app: flannel
template:
metadata:
labels:
tier: node
app: flannel
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/os
operator: In
values:
- linux
- key: kubernetes.io/arch
operator: In
values:
- s390x
hostNetwork: true
priorityClassName: system-node-critical
tolerations:
- operator: Exists
effect: NoSchedule
serviceAccountName: flannel
initContainers:
- name: install-cni
image: quay.io/coreos/flannel:v0.12.0-s390x
command:
- cp
args:
- -f
- /etc/kube-flannel/cni-conf.json
- /etc/cni/net.d/10-flannel.conflist
volumeMounts:
- name: cni
mountPath: /etc/cni/net.d
- name: flannel-cfg
mountPath: /etc/kube-flannel/
containers:
- name: kube-flannel
image: quay.io/coreos/flannel:v0.12.0-s390x
command:
- /opt/bin/flanneld
args:
- --ip-masq
- --kube-subnet-mgr
resources:
requests:
cpu: "100m"
memory: "50Mi"
limits:
cpu: "100m"
memory: "50Mi"
securityContext:
privileged: false
capabilities:
add: ["NET_ADMIN", "NET_RAW"]
env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
volumeMounts:
- name: run
mountPath: /run/flannel
- name: flannel-cfg
mountPath: /etc/kube-flannel/
volumes:
- name: run
hostPath:
path: /run/flannel
- name: cni
hostPath:
path: /etc/cni/net.d
- name: flannel-cfg
configMap:
name: kube-flannel-cfg
先拉取依赖镜像
docker pull quay.io/coreos/flannel:v0.12.0-amd64
把上面文件保存到服务器然后执行下面命令
kubectl apply -f kube-flannel.yml

在master节点(server-a)安装dashboard组件
继续用梯子下载recommended.yml文件
https://raw.githubusercontent.com/kubernetes/dashboard/v2.0.3/aio/deploy/recommended.yaml
没梯子的可以复制下方原文件
# Copyright 2017 The Kubernetes Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
apiVersion: v1
kind: Namespace
metadata:
name: kubernetes-dashboard
---
apiVersion: v1
kind: ServiceAccount
metadata:
labels:
k8s-app: kubernetes-dashboard
name: kubernetes-dashboard
namespace: kubernetes-dashboard
---
kind: Service
apiVersion: v1
metadata:
labels:
k8s-app: kubernetes-dashboard
name: kubernetes-dashboard
namespace: kubernetes-dashboard
spec:
ports:
- port: 443
targetPort: 8443
selector:
k8s-app: kubernetes-dashboard
---
apiVersion: v1
kind: Secret
metadata:
labels:
k8s-app: kubernetes-dashboard
name: kubernetes-dashboard-certs
namespace: kubernetes-dashboard
type: Opaque
---
apiVersion: v1
kind: Secret
metadata:
labels:
k8s-app: kubernetes-dashboard
name: kubernetes-dashboard-csrf
namespace: kubernetes-dashboard
type: Opaque
data:
csrf: ""
---
apiVersion: v1
kind: Secret
metadata:
labels:
k8s-app: kubernetes-dashboard
name: kubernetes-dashboard-key-holder
namespace: kubernetes-dashboard
type: Opaque
---
kind: ConfigMap
apiVersion: v1
metadata:
labels:
k8s-app: kubernetes-dashboard
name: kubernetes-dashboard-settings
namespace: kubernetes-dashboard
---
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
labels:
k8s-app: kubernetes-dashboard
name: kubernetes-dashboard
namespace: kubernetes-dashboard
rules:
# Allow Dashboard to get, update and delete Dashboard exclusive secrets.
- apiGroups: [""]
resources: ["secrets"]
resourceNames: ["kubernetes-dashboard-key-holder", "kubernetes-dashboard-certs", "kubernetes-dashboard-csrf"]
verbs: ["get", "update", "delete"]
# Allow Dashboard to get and update 'kubernetes-dashboard-settings' config map.
- apiGroups: [""]
resources: ["configmaps"]
resourceNames: ["kubernetes-dashboard-settings"]
verbs: ["get", "update"]
# Allow Dashboard to get metrics.
- apiGroups: [""]
resources: ["services"]
resourceNames: ["heapster", "dashboard-metrics-scraper"]
verbs: ["proxy"]
- apiGroups: [""]
resources: ["services/proxy"]
resourceNames: ["heapster", "http:heapster:", "https:heapster:", "dashboard-metrics-scraper", "http:dashboard-metrics-scraper"]
verbs: ["get"]
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
labels:
k8s-app: kubernetes-dashboard
name: kubernetes-dashboard
rules:
# Allow Metrics Scraper to get metrics from the Metrics server
- apiGroups: ["metrics.k8s.io"]
resources: ["pods", "nodes"]
verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
labels:
k8s-app: kubernetes-dashboard
name: kubernetes-dashboard
namespace: kubernetes-dashboard
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: kubernetes-dashboard
subjects:
- kind: ServiceAccount
name: kubernetes-dashboard
namespace: kubernetes-dashboard
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: kubernetes-dashboard
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: kubernetes-dashboard
subjects:
- kind: ServiceAccount
name: kubernetes-dashboard
namespace: kubernetes-dashboard
---
kind: Deployment
apiVersion: apps/v1
metadata:
labels:
k8s-app: kubernetes-dashboard
name: kubernetes-dashboard
namespace: kubernetes-dashboard
spec:
replicas: 1
revisionHistoryLimit: 10
selector:
matchLabels:
k8s-app: kubernetes-dashboard
template:
metadata:
labels:
k8s-app: kubernetes-dashboard
spec:
containers:
- name: kubernetes-dashboard
image: kubernetesui/dashboard:v2.0.3
imagePullPolicy: Always
ports:
- containerPort: 8443
protocol: TCP
args:
- --auto-generate-certificates
- --namespace=kubernetes-dashboard
# Uncomment the following line to manually specify Kubernetes API server Host
# If not specified, Dashboard will attempt to auto discover the API server and connect
# to it. Uncomment only if the default does not work.
# - --apiserver-host=http://my-address:port
volumeMounts:
- name: kubernetes-dashboard-certs
mountPath: /certs
# Create on-disk volume to store exec logs
- mountPath: /tmp
name: tmp-volume
livenessProbe:
httpGet:
scheme: HTTPS
path: /
port: 8443
initialDelaySeconds: 30
timeoutSeconds: 30
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
runAsUser: 1001
runAsGroup: 2001
volumes:
- name: kubernetes-dashboard-certs
secret:
secretName: kubernetes-dashboard-certs
- name: tmp-volume
emptyDir: {}
serviceAccountName: kubernetes-dashboard
nodeSelector:
"kubernetes.io/os": linux
# Comment the following tolerations if Dashboard must not be deployed on master
tolerations:
- key: node-role.kubernetes.io/master
effect: NoSchedule
---
kind: Service
apiVersion: v1
metadata:
labels:
k8s-app: dashboard-metrics-scraper
name: dashboard-metrics-scraper
namespace: kubernetes-dashboard
spec:
ports:
- port: 8000
targetPort: 8000
selector:
k8s-app: dashboard-metrics-scraper
---
kind: Deployment
apiVersion: apps/v1
metadata:
labels:
k8s-app: dashboard-metrics-scraper
name: dashboard-metrics-scraper
namespace: kubernetes-dashboard
spec:
replicas: 1
revisionHistoryLimit: 10
selector:
matchLabels:
k8s-app: dashboard-metrics-scraper
template:
metadata:
labels:
k8s-app: dashboard-metrics-scraper
annotations:
seccomp.security.alpha.kubernetes.io/pod: 'runtime/default'
spec:
containers:
- name: dashboard-metrics-scraper
image: kubernetesui/metrics-scraper:v1.0.4
ports:
- containerPort: 8000
protocol: TCP
livenessProbe:
httpGet:
scheme: HTTP
path: /
port: 8000
initialDelaySeconds: 30
timeoutSeconds: 30
volumeMounts:
- mountPath: /tmp
name: tmp-volume
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
runAsUser: 1001
runAsGroup: 2001
serviceAccountName: kubernetes-dashboard
nodeSelector:
"kubernetes.io/os": linux
# Comment the following tolerations if Dashboard must not be deployed on master
tolerations:
- key: node-role.kubernetes.io/master
effect: NoSchedule
volumes:
- name: tmp-volume
emptyDir: {}
spec:
type: NodePort
ports:
- port: 443
targetPort: 8443
nodePort: 30221
selector:
k8s-app: kubernetes-dashboard
第137行开始,修改账户权限,主要三个参数,kind: ClusterRoleBinding,roleRef-kind: ClusterRole,roleRef-name: cluster-admin
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
labels:
k8s-app: kubernetes-dashboard
name: kubernetes-dashboard
namespace: kubernetes-dashboard
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: cluster-admin
subjects:
- kind: ServiceAccount
name: kubernetes-dashboard
namespace: kubernetes-dashboard
---
保存到服务器后执行以下命令
kubectl apply -f recommended.yaml
等待一段时间启动成功后,https://ip+nodePort,查看UI

Token通过下面指令获取
kubectl -n kubernetes-dashboard get secret
kubectl describe secrets -n kubernetes-dashboard kubernetes-dashboard-token-kfcp2 | grep token | awk 'NR==3{print $2}'

在server-b与server-c执行下面操作
把上面init后的那句join拷贝过来,如果忘记了可以在master节点执行下面指令:
kubeadm token list openssl x509 -pubkey -in /etc/kubernetes/pki/ca.crt | openssl rsa -pubin -outform der 2>/dev/null | openssl dgst -sha256 -hex | sed 's/^.* //'

通过返回的数据拼装成下面指令
kubeadm join 192.168.88.138:6443 --token 2zebwy.1549suwrkkven7ow --discovery-token-ca-cert-hash sha256:c61af74d6e4ba1871eceaef4e769d14a20a86c9276ac0899f8ec6b08b89f532b

查看节点信息
kubectl get node


在master节点(sever-a)执行下面操作
部署应用前建议有需要的朋友到【.Net微服务实战之CI/CD】看看如何搭建docker私有仓库,后面需要用到,搭建后私有库后执行下面指令
kubectl create secret docker-registry docker-registry-secret --docker-server=192.168.88.141:6000 --docker-username=admin --docker-password=123456789
docker-server就是docker私有仓库的地址
下面是yaml模板,注意imagePullSecrets-name与上面的命名的一致,其余的可以查看yaml里的注释
apiVersion: apps/v1
kind: Deployment # Deployment为多个Pod副本
metadata:
name: testdockerswarm-deployment
labels:
app: testdockerswarm-deployment
spec:
replicas: 2 # 实例数量
selector:
matchLabels: # 定义该部署匹配哪些Pod
app: testdockerswarm
minReadySeconds: 3 # 可选,指定Pod可以变成可用状态的最小秒数,默认是0
strategy:
type: RollingUpdate # 部署策略类型,使用RollingUpdate可以保证部署期间服务不间断
rollingUpdate:
maxUnavailable: 1 # 部署时最大允许停止的Pod数量
maxSurge: 1 # 部署时最大允许创建的Pod数量
template: # 用来指定Pod的模板,与Pod的定义类似
metadata:
labels: # Pod标签,与上面matchLabels对应
app: testdockerswarm
spec:
imagePullSecrets:
- name: docker-registry-secret
containers:
- name: testdockerswarm
image: 192.168.88.141:6000/testdockerswarm
imagePullPolicy: Always # Always每次拉去新镜像
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: testdockerswarm-service
labels:
name: testdockerswarm-service
spec:
selector:
app: testdockerswarm #与template-labels参数pod标签一致
ports:
- protocol: TCP
port: 80 #clusterIP开放的端口
targetPort: 80 #container开放的端口,与containerPort一致
nodePort: 31221 # 所有的节点都会开放此端口,此端口供外部调用。
type: NodePort
把yaml文件保存到服务器后执行下面命令
kubectl create -f testdockerswarm.yml


整个搭建部署的过程基本上到这里结束了。
可以通过指令kubectl get service得到ClusterIP,分别在server-c和sever-b执行curl 10.10.184.184

也可以通过执行kubectl get pods -o wide得到pod ip,在server-c执行curl 10.122.2.5 和 server-b执行curl 10.122.1.7
![]()
也可以在外部访问 server-c和server-b的 ip + 31221

如果节点有异常可以通过下面指令排查
journalctl -f -u kubelet.service | grep -i error -C 500
如果Pod无法正常running可以通过下面指令查看
kubectl describe pod testdockerswarm-deployment-7bc647d87d-qwvzm
本文为两类人准备:技术控和工具控。
本文的代码已经不是最新的,但是抓取思路就是如此,可以参考,代码可以直接运行使用,持续维护中。
抖音越来越火,感觉它有毒,越刷越上瘾,总感觉下一个视频一定会更精彩,根本停不下来。想将抖音里喜欢的小哥哥/小姐姐的视频全部存到电脑硬盘里该如何操作?不想有抖音的视频水印该如何处理?
当初写完代码的截屏:
首先,希望你已经具备手机APP抓包分析的能力,如果不会请去自行学习:点击跳转
先说说带水印的视频如何抓去吧。在定好爬取目标的时候,我们应该知道自己需要那些步骤完成这项任务。比如本文中提到的任务:抖音APP固定用户的视频批量下载。
思考过程:
瞧,这样思考下来,问题是不是梳理的很清楚?
搜索接口:
那么接下来就是抓包分析了,抓包过程请自行尝试。步骤是这样的:
通过分析你会发现,我们通过搜索接口返回的JSON数据可以找到用户主页信息,接下里用同样的方法抓取主页用户信息再分析一波,这时候就遇到问题了,你会发现用户主页链接使用了as和cp参数进行了加密,这该如何是好?比如链接如下:
|
1
|
https://aweme.snssdk.com/aweme/v1/aweme/post/?user_id=63386731255&max_cursor=0&count=20…&as=a18575a0311bfa0c2d&cp=55bba65311d10ccde1
|
上述链接省略号部分是一些手机信息,这部分不是必须参数,可以省略。user_id是用户id可以通过上个搜索接口获取,count是用户视频数量,同样可以通过上个搜索接口获取。那最后的as和cp参数怎么办?
我没有逆向抖音APP,就是小小测试了一下,看看能不能绕过这个加密接口?抖音APP自带视频分享功能,分享链接格式如下:
|
1
|
https://www.douyin.com/share/video/6511132370416962829/?region=CN...share_iid=28037626243
|
中间参数都不重要,在此省略。www.douyin.com域名下存放的是分享的视频,那么这个用户主页信息是否可以通过这个域名进行访问呢?小小测试一下你会发现,完全没有问题!
|
1
|
https://www.douyin.com/aweme/v1/aweme/post/?user_id=63386731255&max_cursor=0&count=20
|
这就是没有加密的接口,惊不惊喜,意不意外?根据这个用户主页接口,我们就可以轻松获取用户主页所有的视频链接了。
方法一:
无水印视频下载很简单,有一个通用的方法,就是使用去水印平台即可。
我使用的去水印平台是:http://douyin.iiilab.com/
在输入框中输入视频链接点击视频解析,就可以获得无水印视频链接。
这个网站当初我写代码的时候是好使的,当初用这个网站下了一些无水印视频,不过写这篇文章的时候发现这个取水印平台无法正常解析了,等它修复好了再用这个功能吧。
这个平台不仅包括抖音视频去水印,还支持火山、快手、陌陌、美拍等无水印视频。所以做一个这个网站的接口还是很合适的。
简单测试了一下,这个网站的API是需要付费解析的,如果通过模拟请求的方式有些困难,因此决定上浏览器模拟器Splinter。
Splinter是个好东西,跟Selenium使用类似,它的配置可以参考我的早期Selenium文章:http://blog.csdn.net/c406495762/article/details/72331737
Splinter有个很详细的英文文档:http://splinter.readthedocs.io/en/latest/
这里使用方法就不累述,不过有一点可以说的是,我们可以配置headless参数,来将Splinter配置为无头浏览器,啥事无头浏览器呢?就是运行Splinter不调出浏览器界面,直接在后台模拟各种请求,很是方便。
这部分的代码很简单,无非就是填充元素,确定解析按钮位置,点击按钮,获取视频下载链接即可。这点小问题,就自行分析吧。
整体代码:
|
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
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
|
# -*- coding:utf-8 -*-
from splinter.driver.webdriver.chrome import Options, Chrome
from splinter.browser import Browser
from contextlib import closing
import requests, json, time, re, os, sys, time
from bs4 import BeautifulSoup
class DouYin(object):
def __init__(self, width = 500, height = 300):
“””
抖音App视频下载
“””
# 无头浏览器
chrome_options = Options()
chrome_options.add_argument(‘user-agent=”Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36″‘)
self.driver = Browser(driver_name=‘chrome’, executable_path=‘D:/chromedriver’, options=chrome_options, headless=True)
def get_video_urls(self, user_id):
“””
获得视频播放地址
Parameters:
user_id:查询的用户ID
Returns:
video_names: 视频名字列表
video_urls: 视频链接列表
nickname: 用户昵称
“””
video_names = []
video_urls = []
unique_id = ”
while unique_id != user_id:
search_url = ‘https://api.amemv.com/aweme/v1/discover/search/?cursor=0&keyword=%s&count=10&type=1&retry_type=no_retry&iid=17900846586&device_id=34692364855&ac=wifi&channel=xiaomi&aid=1128&app_name=aweme&version_code=162&version_name=1.6.2&device_platform=Android&ssmix=a&device_type=MI+5&device_brand=Xiaomi&os_api=24&os_version=7.0&uuid=861945034132187&openudid=dc451556fc0eeadb&manifest_version_code=162&resolution=1080*1920&dpi=480&update_version_code=1622’ % user_id
req = requests.get(url = search_url, verify = False)
html = json.loads(req.text)
aweme_count = html[‘user_list’][0][‘user_info’][‘aweme_count’]
uid = html[‘user_list’][0][‘user_info’][‘uid’]
nickname = html[‘user_list’][0][‘user_info’][‘nickname’]
unique_id = html[‘user_list’][0][‘user_info’][‘unique_id’]
user_url = ‘https://www.douyin.com/aweme/v1/aweme/post/?user_id=%s&max_cursor=0&count=%s’ % (uid, aweme_count)
req = requests.get(url = user_url, verify = False)
html = json.loads(req.text)
i = 1
for each in html[‘aweme_list’]:
share_desc = each[‘share_info’][‘share_desc’]
if ‘抖音-原创音乐短视频社区’ == share_desc:
video_names.append(str(i) + ‘.mp4’)
i += 1
else:
video_names.append(share_desc + ‘.mp4’)
video_urls.append(each[‘share_info’][‘share_url’])
return video_names, video_urls, nickname
def get_download_url(self, video_url):
“””
获得带水印的视频播放地址
Parameters:
video_url:带水印的视频播放地址
Returns:
download_url: 带水印的视频下载地址
“””
req = requests.get(url = video_url, verify = False)
bf = BeautifulSoup(req.text, ‘lxml’)
script = bf.find_all(‘script’)[–1]
video_url_js = re.findall(‘var data = \[(.+)\];’, str(script))[0]
video_html = json.loads(video_url_js)
download_url = video_html[‘video’][‘play_addr’][‘url_list’][0]
return download_url
def video_downloader(self, video_url, video_name, watermark_flag=False):
“””
视频下载
Parameters:
video_url: 带水印的视频地址
video_name: 视频名
watermark_flag: 是否下载不带水印的视频
Returns:
无
“””
size = 0
if watermark_flag == True:
video_url = self.remove_watermark(video_url)
else:
video_url = self.get_download_url(video_url)
with closing(requests.get(video_url, stream=True, verify = False)) as response:
chunk_size = 1024
content_size = int(response.headers[‘content-length’])
if response.status_code == 200:
sys.stdout.write(‘ [文件大小]:%0.2f MB\n’ % (content_size / chunk_size / 1024))
with open(video_name, “wb”) as file:
for data in response.iter_content(chunk_size = chunk_size):
file.write(data)
size += len(data)
file.flush()
sys.stdout.write(‘ [下载进度]:%.2f%%’ % float(size / content_size * 100) + ‘\r’)
sys.stdout.flush()
def remove_watermark(self, video_url):
“””
获得无水印的视频播放地址
Parameters:
video_url: 带水印的视频地址
Returns:
无水印的视频下载地址
“””
self.driver.visit(‘http://douyin.iiilab.com/’)
self.driver.find_by_tag(‘input’).fill(video_url)
self.driver.find_by_xpath(‘//button[@class=”btn btn-default”]’).click()
html = self.driver.find_by_xpath(‘//div[@class=”thumbnail”]/div/p’)[0].html
bf = BeautifulSoup(html, ‘lxml’)
return bf.find(‘a’).get(‘href’)
def run(self):
“””
运行函数
Parameters:
None
Returns:
None
“””
self.hello()
user_id = input(‘请输入ID(例如40103580):’)
video_names, video_urls, nickname = self.get_video_urls(user_id)
if nickname not in os.listdir():
os.mkdir(nickname)
print(‘视频下载中:共有%d个作品!\n’ % len(video_urls))
for num in range(len(video_urls)):
print(‘ 解析第%d个视频链接 [%s] 中,请稍后!\n’ % (num+1, video_urls[num]))
if ‘\\’ in video_names[num]:
video_name = video_names[num].replace(‘\\’, ”)
elif ‘/’ in video_names[num]:
video_name = video_names[num].replace(‘/’, ”)
else:
video_name = video_names[num]
self.video_downloader(video_urls[num], os.path.join(nickname, video_name))
print(‘\n’)
print(‘下载完成!’)
def hello(self):
“””
打印欢迎界面
Parameters:
None
Returns:
None
“””
print(‘*’ * 100)
print(‘\t\t\t\t抖音App视频下载小助手’)
print(‘\t\t作者:Jack Cui’)
print(‘*’ * 100)
if __name__ == ‘__main__’:
douyin = DouYin()
douyin.run()
|
方法二:
这个方法是通过网友@羽葵的反馈得知的,对下载链接直接修改即可得到无水印下载链接。
|
1
|
download_url = video_html[‘video’][‘play_addr’][‘url_list’][0].replace(‘playwm’,‘play’)
|
方法简单粗暴,很好用。好处就是处理速度飞快,缺点是这种方法通用性不强,不同视频发布平台的打码方法可能有不同,需要自行分析。
玩爬虫的日子还是很有意思的,好久没有那种舒爽感了。还有,找工作也是蛮心累的事。
更多实战源码,请关注我的Github:https://github.com/Jack-Cherish/python-spider
今天在使用git pull 命令的时候发生了以下报错

目前git的报错提示已经相关友好了,可以直观的发现,这里可以通过commit的方式解决这个冲突问题,但还是想看看其他大佬是怎么解决这类问题的
在网上查了资料和其他大佬的博客,得到了两种解决方法:
方法一、stash
1 git stash 2 git commit 3 git stash pop
接下来diff一下此文件看看自动合并的情况,并作出相应修改。
git stash: 备份当前的工作区的内容,从最近的一次提交中读取相关内容,让工作区保证和上次提交的内容一致。同时,将当前的工作区内容保存到Git栈中。
git stash pop: 从Git栈中读取最近一次保存的内容,恢复工作区的相关内容。由于可能存在多个Stash的内容,所以用栈来管理,pop会从最近的一个stash中读取内容并恢复。
git stash list: 显示Git栈内的所有备份,可以利用这个列表来决定从那个地方恢复。
git stash clear: 清空Git栈。此时使用gitg等图形化工具会发现,原来stash的哪些节点都消失了。
方法二、放弃本地修改,直接覆盖
1 git reset --hard 2 git pull
参考原文:https://blog.csdn.net/lincyang/article/details/21519333