而Pyppeteer和Selenium就是用的第三种方法,下面我们再用Pyppeteer来试试,如果用Pyppeteer实现如上页面的抓取的话,代码就可以写为如下形式:
好了,知道这些参数之后,我们可以先试试看。首先可以试用下最常用的参数headless,如果我们将它设置为True或者默认不设置它,在启动的时候我们是看不到任何界面的,如果把它设置为False,那么在启动的时候就可以看到界面了,一般我们在调试的时候会把它设置为False,在生产环境上就可以设置为True,我们先尝试一下关闭headless模式:
公众号”进击的Coder”回复”Pyppeteer”即可获取本节全部代码。
【顺手提供】精通正则表达式:第三版PDF(高清-中文-带标签)
1logging.Formatter.__init__(fmt=None,datefmt=None,style='%')该构造方法接收3个可选参数:
FilterFilter可以被Handler和Logger用来做比level更细粒度的、更复杂的过滤功能。Filter是一个过滤器基类,它只允许某个logger层级下的日志事件通过过滤。该类定义如下:
12classlogging.Filter(name='')filter(record)比如,一个filter实例化时传递的name参数值为’A.B’,那么该filter实例将只允许名称为类似如下规则的loggers产生的日志记录通过过滤:’A.B’,’A.B,C’,’A.B.C.D’,’A.B.D’,而名称为’A.BB’,‘B.A.B’的loggers产生的日志则会被过滤掉。如果name的值为空字符串,则允许所有的日志事件通过过滤。filter方法用于具体控制传递的record记录是否能通过过滤,如果该方法返回值为0表示不能通过过滤,返回值为非0表示可以通过过滤。
排序结果就是该十进制数的二进制表示。例如十进制数101转换为二进制数的计算过程如下:
现在,我们已经了解到二进制与十进制的换算方法,并拥有了进制对照表。但在开始学习位运算符之前,我们还需要了解补码的知识。数值有正负之分,那么仅有0和1的二进制如何表示正负呢?人们设定,二进制中最高位为0代表正,为1则代表负。例如00001100对应的十进制为12,而10001100对应的十进制为\-12。这种表示被称作原码。但新的问题出现了,原本二进制的最高位始终为0,为了表示正负又多出了1,在执行运算时就会出错。举个例子,1+(-2)的二进制运算如下:
12300000001+10000010=10000011=-3这显然是有问题的,问题就处在这个代表正负的最高位。接着,人们又弄出了反码(二进制各位置的0与1互换,例如00001100的反码为11110011)。此时,运算就会变成这样:
1234500000001+11111101=11111110#在转换成十进制前,需要再次反码=10000001=-1这次好像正确了。但它仍然有例外,我们来看一下1+(-1):
123400000001+1111+1110=11111111=10000000=-0零是没有正负之分的,为了解决这个问题,就搞出了补码的概念。补码是为了让负数变成能够加的正数,所以负数的补码=负数的绝对值取反+1,例如\-1的补码为:
123456#1的补码+-6的补码00000001+11111010=11111011#补码运算结果=11111010#对补码减1,得到反码=10000101#反码取反,得到原码=-5#对应的十进制123456#1的补码+-9的补码00000001+11110111=11111000#补码运算结果=11110111#对补码减1,得到反码=10001000#反码取反,得到原码=-8#对应的十进制要注意的是,正数的补码与原码相同,不需要额外运算。也可以说,补码的出现就是为了解决负数运算时的符号问题。
位运算分为6种,它们是:
名称
符号
按位与
&
按位或
|
按位异或
^
按位取反
~
左移运算
<<
右移运算
>>
按位与运算将参与运算的两数对应的二进制位相与,当对应的二进制位均为1时,结果位为1,否则结果位为0。按位与运算的运算符为&,参与运算的数以补码方式出现。举个例子,将数字5和数字8进行按位与运算,其实是将数字5对应的二进制00000101和数字8对应的二进制00001000进行按位与运算,即:
12300000101&00001000根据按位与的规则,将各个位置的数进行比对。运算过程如下:
按位或运算将参与运算的两数对应的二进制位相或,只要对应的二进制位中有1,结果位为1,否则结果位为0。按位或运算的运算符为|,参与运算的数以补码方式出现。举个例子,将数字3和数字7进行按位或运算,其实是将数字3对应的二进制00000011和数字7对应的二进制00000111进行按位或运算,即:
12300000011|00000111根据按位或的规则,将各个位置的数进行比对。运算过程如下:
1234500000011|00000111--------00000111最终得到的结果为00000111。将结果换算成十进制,得到7,即3|7=7。
按位异或运算将参与运算的两数对应的二进制位相异或,当对应的二进制位值不同时,结果位为1,否则结果位为0。按位异或的运算符为^,参与运算的数以补码方式出现。举个例子,将数字12和数字7进行按位异或运算,其实是将数字12对应的二进制00001100和数字7对应的二进制00000111进行按位异或运算,即:
12300001100^00000111根据按位异或的规则,将各个位置的数进行比对。运算过程如下:
1234500001100^00000111--------00001011最终得到的结果为00001011。将结果换算成十进制,得到11,即12^7=11。
按位取反运算将二进制数的每一个位上面的0换成1,1换成0。按位取反的运算符为~,参与运算的数以补码方式出现。举个例子,对数字9进行按位取反运算,其实是将数字9对应的二进制00001001进行按位取反运算,即:
1234~00001001=00001001#补码,正数补码即原码=11111010#取反=-10最终得到的结果为\-10。再来看一个例子,\-20按位取反的过程如下:
1234~00010100=11101100#补码=00010011#取反=19最终得到的结果为19。我们从示例中找到了规律,按位取反的结果用数学公式表示:我们可以将其套用在9和\-20上:
12~9=-(9+1)=-10~(-20)=-((-20)+1)=19这个规律也可以作用于数字0上,即~0=-(0+1)=-1。
左移运算将数对应的二进位全部向左移动若干位,高位丢弃,低位补0。左移运算的运算符为<<。举个例子,将数字5左移4位,其实是将数字5对应的二进制00000101中的二进位向左移动4位,即:
右移运算将数对应的二进位全部向右移动若干位。对于左边的空位,如果是正数则补0,负数可能补0或1(TurboC和很多编译器选择补1)。右移运算的运算符为\>>。举个例子,将数字80右移4位,其实是将数字80对应的二进制01010000中的二进位向右移动4位,即:
123480>>4=01010000>>4=00000101#正数补0,负数补1=5最终结果为5。这等效于:也就是说,右移运算的规律为:要注意的是,不能整除时,取整数。这中除法取整的规则类似于PYTHON语言中的地板除。
12345#pythonif101%2:print('偶数')else:print('奇数')我们也可以通过位运算中的按位与来实现奇偶判断,例如:
12345#pythonif101&1:print('奇数')else:print('偶数')这是因为奇数的二进制最低位始终为1,而偶数的二进制最低为始终为0。所以,无论任何奇数与1即00000001相与得到的都是1,任何偶数与其相与得到的都是0。变量交换在C语言中,两个变量的交换必须通过第三个变量来实现。伪代码如下:
1234567#伪代码a=3,b=5c=aa=bb=a--------a=5,b=3在PYTHON语言中并没有这么麻烦,可以直接交换。对应的PYTHON代码如下:
1234#pythona,b=3,5a,b=b,aprint(a,b)代码运行结果为53。但大部分编程语言都不支持PYTHON这种写法,在这种情况下我们可以通过位运算中的按位异或来实现变量的交换。对应的伪代码如下:
12345#伪代码a=3,b=5a=a^bb=a^ba=a^b最后,a=5,b=3。我们可以用C语言和PYTHON语言进行验证,对应的PYTHON代码如下:
123456#pythona,b=3,5a=a^bb=a^ba=a^bprint(a,b)代码运行结果为53,说明变量交换成功。对应的C代码如下:
12交换前:a=3,b=5交换后:a=5,b=3这说明变量交换成功。求x与2的n次方乘积设一个数为x,求x与2的n次方乘积。这用数学来计算都是非常简单的:在位运算中,要实现这个需求只需要用到左移运算,即x<
1234#pythonx=5#00000101foriinrange(8):print(x>>i&1)代码运行结果如下:
1234567810100000这说明位运算的算法是正确的,可以满足我们的需求。判断赋值
1234ifa==x:x=belse:x=a等效于x=a^b^x。我们可以通过PYTHON代码来验证:
1234567#pythona,b,x=6,9,6ifa==x:x=belse:x=aprint(a,b,x)代码运行结果为699,与之等效的代码如下:
上周五,大师兄发给我一个网址,哭哭啼啼地求我:“去!把这个网页上所有年所有县所有作物的数据全爬下来,存到Access里!”我看他可怜,勉为其难地挥挥手说:“好嘞,马上就开始!”
1234response1=requests.get(url,headers=headers)ifresponse1.status_code==200:cookies=response1.cookiesprint(cookies)输出:
1
现在既然已经拿到了目标页面的HTML,那在获取所有年、地区、州名、县名之前,先测试一下pandas.read_html提取网页表格的功能。pandas.read_html这个函数时在写代码时IDE自动补全下拉列表里瞄到的,一直想试试来着,今天乘机拉出来溜溜:
123456789101112remain=[[str(year),str(region),str(state),str(county)]foryearinyearsforregioninregionsforstateinregion_state[region]forcountyinstate_county[state]]remain=pd.DataFrame(remain,columns=['CRMSearchForm[year]','CRMSearchForm[region]','CRMSearchForm[state]','CRMSearchForm[county]'])remain.to_csv('remain.csv',index=False)#由于州名有缩写和全称,也本地保存一份importjsonwithopen('region_state.json','w')asjson_file:json.dump(region_state,json_file,indent=4)我看了一下,一共49473行——也就是说至少要发送49473个post请求才能爬完全部数据,纯手工获取的话大概要点击十倍这个数字的次数……
那么开始爬咯
周一,我把跑出来的数据发给大师兄,大师兄回我:“好的”。隔着屏幕我都能感受到滔滔不绝的敬仰和感激之情,一直到现在,大师兄都感动地说不出话来。
要使用Axios,你需要先安装它。
1npminstallaxios下面是一个演示Axios行动的基本例子。
输入命令查看JDK安装路径:/usr/libexec/java_home-V
121.8.0_60,x86_64:"JavaSE8"/Library/Java/JavaVirtualMachines/jdk1.8.0_60.jdk/Contents/Home/Library/Java/JavaVirtualMachines/jdk1.8.0_60.jdk/Contents/Home需要把上面的路径配置到环境变量中,ANDROID_HOME就是AndroidSDK的安装路径。
输入命令打开配置文件:open~/.bash_profile,在文件中添加如下内容:
123exportJAVA_HOME=/Library/Java/JavaVirtualMachines/jdk1.8.0_60.jdk/Contents/HomeexportPATH=$JAVA_HOME/bin:$PATHexportANDROID_HOME=/Users/chenwenguan/Library/Android/sdk输入命令让配置立即生效:source~/.bash_profile
在首次使用Appium时可能会出现一个错误:
1CouldnotdetectMacOSXVersionfromsw_versoutput:'10.13.2在终端输入命令:
1grep-rl"CouldnotdetectMacOSXVersionfromsw_versoutput"/Applications/Appium.app/得到如下结果:
在AndroidStudio打开Monitor工具:Tools->Android->AndroidDeviceMonitor按照下图的步骤查看控件的ID等属性,后续在代码实现中会用到。
AndroidDeviceMonitor
chromeinspect页面
Chromeinspect查看WebView详细内容
设备信息
AppiumURL参数设置
不管是WebView还是Android原生ListView的滑动都需要在Android原生上下文环境下操作driver.context(“NATIVE_APP”);滑动操作都可以通过如下代码实现,通过滑动前后的PageSource对比可以知道列表是否已经滑动到底部。
1driver.findElement(By.xpath("//android.widget.ImageView[@content-desc='返回']")).click();时候经常出现如下错误,改为
1driver.findElementById("com.tencent.mm:id/ht").click();异常消失,猜测原因就是因为By.xpath()方法查找比较耗时导致。
自动化脚本程序要跑起来需要两个压缩包,java-client-3.1.0.jar和selenium-server-standalone-2.44.0.jar,试过使用这两个JAR包的最新版本,会有一些奇奇怪怪的问题,这两个版本的JAR包够用了。java-client-3.1.0.jar可以从Appium官网下载:
123java-client-3.1.0.jarselenium-server-standalone-2.44.0.jarAppiumWeChatAuto/appiumauto/src/main/java/com/example/AppiumAutoScan.javaEclipseIDE下载地址:
那么前端完成一个合格的验证码,究竟需要做成什么样子呢?
以上就是验证码的两个基本要求,所以我们这里就来实现一下看看。
下面就具体讲解下这个是怎么实现的,实际上核心代码只有200行,下面对整个核心流程进行说明。既然Vue这么火,那我这里就用Vue来实现啦,具体的环境配置这里就不再赘述了,需要安装的有:
安装完成之后便可以使用vue命令了,新建个项目:
对于Drop组件来说,它是一个被放置的对象,被拖动滑块会放到这个Drop滑块上,这就代表拖动成功了。它有两个主要的事件需要监听,一个叫做dragover,一个叫做dragleave,分别用来监听Drag对象拖上和拖开的事件。在这里,分别对两个事件设置了onDragOver和onDragLeave的回调函数,当Drag对象放到Drop对象上面的时候,就会触发onDragOver对象,当拖开的时候就会触发onDragLeave事件。那这样的话我们只需要一个全局变量来记录是否已经将滑块拖动到目标位置即可,比如可以定一个全局变量state,我们用over属性来代表是否拖动到目标位置。因此onDragOver和onDragLeave事件可以这么实现:
1
所以,ModelZoo诞生了!
开发的时候,我自己首先先实现了一些基本的模型,使用的是TensorFlowEager和Keras,然后试着抽离出来一些公共部分,将其封装成基础类,同时把模型独有的实现放开,供子类复写。然后在使用过程中自己还封装和改写过一些工具类,这部分也集成进来。另外就是把一些配置都规范化,将一些常用参数配置成默认参数,同时放开重写开关,在外部可以重定义。秉承着上面的思想,我大约是在10月6日那天完成了框架的搭建,然后在后续的几天基于这个框架实现了几个基础模型,最终打磨成了现在的样子。
1pip3installmodel-zoo其实我是很震惊,这个名字居然没有被注册!GitHub和PyPi都没有!不过现在已经被我注册了。OK,接下来让我们看看用了它能怎样快速搭建一个模型吧!我们就以基本的线性回归模型为例来说明吧,这里有一组数据,是波士顿房价预测数据,输入是影响房价的各个因素,输出是房价本身,具体的数据集可以搜Bostonhousingpriceregressiondataset了解一下。总之,我们只需要知道这是一个回归模型就好了,输入x是一堆Feature,输出y是一个数值,房价。好,那么我们就开始定义模型吧,模型的定义我们继承ModelZoo里面的BaseModel就好了,实现model.py如下:
12defoptimizer(self):returntf.train.AdamOptimizer(0.001)好,定义了模型之后怎么办?那当然是拿数据训练了,又要写数据加载,数据标准化,数据切分等等操作了吧,写到什么方法里?定义成什么样比较科学?现在,我们只需要实现一个Trainer就好了,然后复写prepare_data方法就好了,实现train.py如下:
1tf.flags.DEFINE_string('checkpoint_dir','checkpoints',help='Datasourcedir')好了,现在模型可以训练了!直接运行上面的代码就好了:
1python3train.py结果是这样子的:
1234567891011121314151617frommodel_zoo.infererimportBaseInfererfrommodel_zoo.preprocessimportstandardizeimporttensorflowastftf.flags.DEFINE_string('checkpoint_name','model.ckpt-20',help='Modelname')classInferer(BaseInferer):defprepare_data(self):fromtensorflow.python.keras.datasetsimportboston_housing(x_train,y_train),(x_test,y_test)=boston_housing.load_data()_,x_test=standardize(x_train,x_test)returnx_testif__name__=='__main__':result=Inferer().run()print(result)这里只需要继承BaseInferer,实现prepare_data方法就好了,返回的就是test数据集的x部分,其他的还是什么都不用干!另外这里额外定义了一个Flag,就是checkpoint_name,这个是必不可少的,毕竟要用哪个Checkpoint需要指定一下。这里我们还是那数据集中的数据当测试数据,来看下它的输出结果:
123456789101112131415[[9.637125][21.368305][20.898445][33.832504][25.756516][21.264557][29.069794][24.968184]...[36.027283][39.06852][25.728745][41.62165][34.340042][24.821484]]就这样,预测房价结果就计算出来了,这个和输入的x内容都是一一对应的。那有人又说了,我如果想拿到模型中的某个变量结果怎么办?还是很简单,因为有了Eager模式,直接输出就好。我要自定义预测函数怎么办?也很简单,复写infer方法就好了。好,到现在为止,我们通过几十行代码就完成了这些内容:
总而言之,用了这个框架可以省去很多不必要的麻烦,同时相对来说比较规范,另外灵活可扩展。以上就是ModelZoo的一些简单介绍。
属性说明如下:
我们现在来解决上面的示例,代码如下:
123456789101112131415161718fromsklearn.linear_modelimportLogisticRegressionx_data=[[6000,58],[9000,77],[11000,89],[15000,54]]y_data=[0,0,1,1]lr=LogisticRegression()lr.fit(x_data,y_data)x_test=[[12000,60]]print('Intercept',lr.intercept_)print('Coef',lr.coef_)print('款项是否可以立即到账',lr.predict(x_test)[0])这里我们的y_data数据就变了,变成了是非判断,这里0代表“否”,1代表“是”。这里做逻辑回归时使用了LogisticRegression对象,然后调用fit()方法进行拟合,拟合完毕之后,使用[12000,60]这条数据作为输入,让模型输出它的预测值。运行结果:
123Intercept[-0.03142387]Coef[[0.00603919-0.72587703]]款项是否可以立即到账1结果中输出了截距项和系数项,然后预测了是否可以立即到账,结果为1,这表明孙七进行贷款,款项可以立即到账。以上就是逻辑回归的基本推导和代码应用实现。
属性如下:
代码实现如下:
OK,理清了这几部分内容,我们继续往下深入了解它的用法吧。
123456fromattrimportattrs,attrib@attrsclassPoint(object):x=attrib()y=attrib()其中attrib里面什么参数都没有,如果我们要使用的话,参数可以顺次指定,也可以根据名字指定,如:
1234p1=Point(1,2)print(p1)p2=Point(x=1,y=2)print(p2)其效果都是一样的,打印输出结果如下:
12Point(x=1,y=2)Point(x=1,y=2)OK,接下来让我们再验证下类之间的比较方法,由于使用了attrs,相当于我们定义的类已经有了__eq__、__ne__、__lt__、__le__、__gt__、__ge__这几个方法,所以我们可以直接使用比较符来对类和类之间进行比较,下面我们用实例来感受一下:
123456print('Equal:',Point(1,2)==Point(1,2))print('NotEqual(ne):',Point(1,2)!=Point(3,4))print('LessThan(lt):',Point(1,2)
现在看来,对于这个类的定义莫过于每个属性的定义了,也就是attrib的定义。对于attrib的定义,我们可以传入各种参数,不同的参数对于这个类的定义有非常大的影响。下面我们就来详细了解一下每个属性的具体参数和用法吧。首先让我们概览一下总共可能有多少可以控制一个属性的参数,我们用attrs里面的fields方法可以查看一下:
12345678fromattrimportattrs,attrib,fields@attrsclassPoint(object):x=attrib()y=attrib()print(fields(Point))这就可以输出Point的所有属性和对应的参数,结果如下:
对于属性名,非常清楚了,我们定义什么属性,属性名就是什么,例如上面的例子,定义了:
1x=attrib()那么其属性名就是x。
对于默认值,如果在初始化的时候没有指定,那么就会默认使用默认值进行初始化,我们看下面的一个实例:
12345678910fromattrimportattrs,attrib,fields@attrsclassPoint(object):x=attrib()y=attrib(default=100)if__name__=='__main__':print(Point(x=1,y=3))print(Point(x=1))在这里我们将y属性的默认值设置为了100,在初始化的时候,第一次都传入了x、y两个参数,第二次只传入了x这个参数,看下运行结果:
12Point(x=1,y=3)Point(x=1,y=100)可以看到结果,当设置了默认参数的属性没有被传入值时,他就会使用设置的默认值进行初始化。那假如没有设置默认值但是也没有初始化呢?比如执行下:
1Point()那么就会报错了,错误如下:
1TypeError:__init__()missing1requiredpositionalargument:'x'所以说,如果一个属性,我们一旦没有设置默认值同时没有传入的话,就会引起错误。所以,一般来说,为了稳妥起见,设置一个默认值比较好,即使是None也可以的。
如果一个类的某些属性不想参与初始化,比如想直接设置一个初始值,一直固定不变,我们可以将属性的init参数设置为False,看一个实例:
123456789fromattrimportattrs,attrib@attrsclassPoint(object):x=attrib(init=False,default=10)y=attrib()if__name__=='__main__':print(Point(3))比如x我们只想在初始化的时候设置固定值,不想初始化的时候被改变和设定,我们将其设置了init参数为False,同时设置了一个默认值,如果不设置默认值,默认为NOTHING。然后初始化的时候我们只传入了一个值,其实也就是为y这个属性赋值。这样的话,看下运行结果:
1Point(x=10,y=3)没什么问题,y被赋值为了我们设置的值3。那假如我们非要设置x呢?会发生什么,比如改写成这样子:
1Point(1,2)报错了,错误如下:
1TypeError:__init__()takes2positionalargumentsbut3weregiven参数过多,也就是说,已经将init设置为False的属性就不再被算作可以被初始化的属性了。
强制关键字是Python里面的一个特性,在传入的时候必须使用关键字的名字来传入,如果不太理解可以再了解下Python的基础。设置了强制关键字参数的属性必须要放在后面,其后面不能再有非强制关键字参数的属性,否则会报这样的错误:
1ValueError:Nonkeyword-onlyattributesarenotallowedafterakeyword-onlyattribute(unlesstheyareinit=False)好,我们来看一个例子,我们将最后一个属性设置kw_only参数为True:
123456789fromattrimportattrs,attrib,fields@attrsclassPoint(object):x=attrib(default=0)y=attrib(kw_only=True)if__name__=='__main__':print(Point(1,y=3))如果设置了kw_only参数为True,那么在初始化的时候必须传入关键字的名字,这里就必须指定y这个名字,运行结果如下:
1Point(x=1,y=3)如果没有指定y这个名字,像这样调用:
1Point(1,3)那么就会报错:
1TypeError:__init__()takesfrom1to2positionalargumentsbut3weregiven所以,这个参数就是设置初始化传参必须要用名字来传,否则会出现错误。注意,如果我们将一个属性设置了init为False,那么kw_only这个参数会被忽略。
有时候在设置一个属性的时候必须要满足某个条件,比如性别必须要是男或者女,否则就不合法。对于这种情况,我们就需要有条件来控制某些属性不能为非法值。下面我们看一个实例:
这是三个参数是固定的,在类初始化的时候,其内部会将这三个参数传递给这个Validator,因此Validator里面就可以接受到这三个值,然后进行判断即可。在Validator里面,我们判断如果不是男性或女性,那么就直接抛出错误。下面做了两个实验,一个就是正常传入male,另一个写错了,写的是mlae,观察下运行结果:
12Person(name='Mike',gender='male')TypeError:__init__()missing1requiredpositionalargument:'gender'OK,结果显而易见了,第二个报错了,因为其值不是正常的性别,所以程序直接报错终止。注意在Validator里面返回True或False是没用的,错误的值还会被照常复制。所以,一定要在Validator里面raise某个错误。另外attrs库里面还给我们内置了好多Validator,比如判断类型,这里我们再增加一个属性age,必须为int类型:
1age=attrib(validator=validators.instance_of(int))这时候初始化的时候就必须传入int类型,如果为其他类型,则直接抛错:
123456789101112131415fromattrimportattrs,attribdefto_int(value):try:returnint(value)except:returnNone@attrsclassPoint(object):x=attrib(converter=to_int)y=attrib()if__name__=='__main__':print(Point('100',3))看这里,我们定义了一个方法,可以将值转化为数字类型,如果不能转,那么就返回None,这样保证了任何可以被转数字的值都被转为数字,否则就留空,容错性非常高。运行结果如下:
12345678910fromattrimportattrs,attrib@attrsclassPoint(object):x=attrib(type=int)y=attrib()if__name__=='__main__':print(Point(100,3))print(Point('100',3))这里我们将x属性定义为int类型了,初始化的时候传入了数值型100和字符串型100,结果如下:
12Point(x=100,y=3)Point(x='100',y=3)但我们发现,虽然定义了,但是不会被自动转类型的。另外我们还可以自定义typing里面的类型,比如List,另外attrs里面也提供了类型的定义:
12345678fromattrimportattrs,attrib,Factoryimporttyping@attrsclassPoint(object):x=attrib(type=int)y=attrib(type=typing.List[int])z=attrib(type=Factory(list))这里我们引入了typing这个包,定义了y为int数字组成的列表,z使用了attrs里面定义的Factory定义了同样为列表类型。另外我们也可以进行类型的嵌套,比如像这样子:
12[Point(x=0,y=0),Point(x=1,y=1),Point(x=2,y=2),Point(x=3,y=3),Point(x=4,y=4)]Line(name='line1',points=[Point(x=0,y=0),Point(x=1,y=1),Point(x=2,y=2),Point(x=3,y=3),Point(x=4,y=4)])可以看到这里我们得到了一个嵌套类型的Line对象,其值是Point类型组成的列表。以上便是一些属性的定义,把握好这些属性的定义,我们就可以非常方便地定义一个类了。
在很多情况下,我们经常会遇到JSON等字符串序列和对象互相转换的需求,尤其是在写RESTAPI、数据库交互的时候。attrs库的存在让我们可以非常方便地定义Python类,但是它对于序列字符串的转换功能还是比较薄弱的,cattrs这个库就是用来弥补这个缺陷的,下面我们再来看看cattrs这个库。cattrs导入的时候名字也不太一样,叫做cattr,它里面提供了两个主要的方法,叫做structure和unstructure,两个方法是相反的,对于类的序列化和反序列化支持非常好。
首先我们来看看基本的转换方法的用法,看一个基本的转换实例:
1234567891011121314fromattrimportattrs,attribfromcattrimportunstructure,structure@attrsclassPoint(object):x=attrib(type=int,default=0)y=attrib(type=int,default=0)if__name__=='__main__':point=Point(x=1,y=2)json=unstructure(point)print('json:',json)obj=structure(json,Point)print('obj:',obj)在这里我们定义了一个Point对象,然后调用unstructure方法即可直接转换为JSON字符串。如果我们再想把它转回来,那就需要调用structure方法,这样就成功转回了一个Point对象。看下运行结果:
另外structure也支持一些其他的类型转换,看下实例:
上面的例子都是理想情况下使用的,但在实际情况下,很容易遇到JSON和对象不对应的情况,比如JSON多个字段,或者对象多个字段。我们先看看下面的例子:
1TypeError:__init__()gotanunexpectedkeywordargument'z'不出所料,报错了。意思是多了一个参数,这个参数并没有被定义。这时候一般的解决方法的直接忽略这个参数,可以重写一下structure方法,定义如下:
1234567891011121314151617181920importdatetimefromattrimportattrs,attribimportcattrTIME_FORMAT='%Y-%m-%dT%H:%M:%S.%fZ'@attrsclassEvent(object):happened_at=attrib(type=datetime.datetime)cattr.register_unstructure_hook(datetime.datetime,lambdadt:dt.strftime(TIME_FORMAT))cattr.register_structure_hook(datetime.datetime,lambdastring,_:datetime.datetime.strptime(string,TIME_FORMAT))event=Event(happened_at=datetime.datetime(2019,6,1))print('event:',event)json=cattr.unstructure(event)print('json:',json)event=cattr.structure(json,Event)print('Event:',event)在这里我们对datetime这个类型注册了两个hook,当序列化的时候,就调用strftime方法转回字符串,当反序列化的时候,就调用strptime将其转回datetime类型。看下运行结果:
最后我们再来看看嵌套类型的处理,比如类里面有个属性是另一个类的类型,如果遇到这种嵌套类的话,怎样类转转换呢?我们用一个实例感受下:
123456789101112131415161718192021222324252627fromattrimportattrs,attribfromtypingimportListfromcattrimportstructure,unstructure@attrsclassPoint(object):x=attrib(type=int,default=0)y=attrib(type=int,default=0)@attrsclassColor(object):r=attrib(default=0)g=attrib(default=0)b=attrib(default=0)@attrsclassLine(object):color=attrib(type=Color)points=attrib(type=List[Point])if__name__=='__main__':line=Line(color=Color(),points=[Point(i,i)foriinrange(5)])print('Object:',line)json=unstructure(line)print('JSON:',json)line=structure(json,Line)print('Object:',line)这里我们定义了两个Class,一个是Point,一个是Color,然后定义了Line对象,其属性类型一个是Color类型,一个是Point类型组成的列表,下面我们进行序列化和反序列化操作,转成JSON然后再由JSON转回来,运行结果如下:
本节介绍了利用attrs和cattrs两个库实现Python面向对象编程的实践,有了它们两个的加持,Python面向对象编程不再是难事。
1234567891011121314defcall_weather_api(url,location):"""Gettheweatherofspecificlocation.Callingweatherapitocheckforweatherbyusingweatherapiandlocation.Makesureyouprovidecitynameonly,countryandcountynameswon'tbeacceptedandwillthrowexceptionifnotfoundthecityname.:paramurl:URLoftheapitogetweather.:typeurl:str:paramlocation:Locationofthecitytogettheweather.:typelocation:str:return:Givetheweatherinformationofgivenlocation.:rtype:str"""说一下上面代码的注意点
一般在文件的顶部放置一个模块级的docstring来简要描述模块的使用。这些注释应该放在在导包之前,模块文档字符串应该表明模块的使用方法和功能。如果觉得在使用模块之前客户端需要明确地知道方法或类,你还可以简要地指定特定方法或类。
1234567891011"""Thismodulecontainsallofthenetworkrelatedrequests.Thismodulewillcheckforalltheexceptionswhilemakingthenetworkcallsandraiseexceptionsforanyunknownexception.Makesurethatwhenyouusethismodule,youhandletheseexceptionsinclientcodeas:NetworkErrorexceptionfornetworkcalls.NetworkNotFoundexceptionifnetworknotfound."""importurllib3importjson在为模块编写文档字符串时,应考虑执行以下操作:
12NetworkErrorexceptionfornetworkcalls.NetworkNotFoundexceptionifnetworknotfound.1234classStudent:"""Thisclasshandleactionsperformedbyastudent."""def__init__(self):pass这个类有一个一行的docstring,它简要地讨论了学生类。如前所述,遵守了所以一行docstring的编码规范。
1234567891011121314classStudent:"""Studentclassinformation.Thisclasshandleactionsperformedbyastudent.Thisclassprovidesinformationaboutstudentfullname,age,roll-numberandotherinformation.Usage:importstudentstudent=student.Student()student.get_name()>>>678998"""def__init__(self):pass这个类docstring是多行的;我们写了很多关于Student类的用法以及如何使用它。
函数文档字符串可以写在函数之后,也可以写在函数的顶部。
123456789101112defis_prime_number(number):"""Checkforprimenumber.Checkthegivennumberisprimenumberornotbycheckingagainstallthenumberslessthesquarerootofgivennumber.:paramnumber:Givennumbertocheckforprime:typenumber:int:return:TrueifnumberisprimeotherwiseFalse.:rtype:boolean"""如果我们使用类型注解对其进一步优化。
Python是不是第一先不说,就看看上面两句话的排版,哪个看起来更舒服?说实话我是真觉得第一句话太别扭了。因为我们大部分的文本编辑器和浏览器是没有对中文和外文的混排做排版优化的,所以如果写的时候如果二者之间不加个空格,二者就会紧紧贴在一起,然后就变成了上面第一句的样子。当然如果你觉得第一句的排版更好看,好吧,那么本文后面的内容其实可以不必看了。OK,如果你觉得第二个好看,那不妨接着看下去哈。
中英文之间是需要添加空格的,不论是普通英文还是引用的英文,下面给个示例:正确:
错误:
完整的正确用法:
但有例外,比如「豆瓣FM」等产品名词,按照官方所定义的格式书写。再比如,我的公众号为「进击的Coder」,那么这里面就不要加空格,按照其本身的形式书写即可。
中文和数字之间也是需要的,下面给个示例:正确:
但是数字和单位之间不需要再加额外的空格了,下面给个正确:
另外,度/百分比与数字之间不需要增加空格:正确:
标点是分全角和半角的,全角标点一般是在中文状态下输出来的,比如,、。、!,半角标点一般是在英文状态下输出来的,比如,、.、!,两个看起来不一样吧?所以,如果是中文标点,即全角标点,那不需要加空格。正确:
嗯,基本就是以上的几个规范,只要明白了这些规范,中英文混排就OK了!
那么有编辑器支持这个吗?有,MicrosoftWord,用它我们不用加空格,会自动给我们加好间距。有人说,我平时不想用Word,我就想用Markdown,有编辑器吗?有,叫做MarkEditor,它的2.0Pro版本可以在打字的时候自动给我们添加空格。注意,这里是自动添加空格,不是自动留间距,是用空格的方式实现了间距。但是这个只能在你一个个打字的时候自动添加空格,如果把一个不带空格的话粘贴进去是不行的。另外MarkEditor解锁这个功能需要付费,所以我个人感觉其实不太划算的。所以,平时还是自己手动加空格吧,经济实惠方便。其他的编辑器如有好用的欢迎大家推荐哈。
好吧,看到现在,你是不是现在都想把自己的中英文笔记加上空格了?难道要手调吗?不需要。有现成的工具了,名字叫做pangu,它支持各种语言,另外还有浏览器插件可以用,列表如下:
1pip3install-Upangu这么用就好了:
12importpanguprint(pangu.spacing_text('當你凝視著bug,bug也凝視著你'))运行结果如下:
它有这么几个特性:
是不是等不及了呢?那就让我们来了解一下它的用法吧!
首先就是这个库的安装了,通过pip安装即可:
1pip3installpypinyin安装完成之后导入一下这个库,如果不报错,那就说明安装成功了。
1\>>>importpypinyin好,接下来我们看下它的具体功能。
首先我们进行一下基本的拼音转换,方法非常简单,直接调用pinyin方法即可:
12frompypinyinimportpinyinprint(pinyin('中心'))运行结果:
1[['zhōng'],['xīn']]可以看到结果会是一个二维的列表,每个元素都另外成了一个列表,其中包含了每个字的读音。那么如果这个词是多音字咋办呢?比如“朝阳”,它有两个读音,我们拿来试下:
12frompypinyinimportpinyinprint(pinyin('朝阳'))运行结果:
1[['zhāo'],['yáng']]好吧,它只给出来了一个读音,但是如果我们想要另外一种读音咋办呢?其实很简单,只需添加heteronym参数并设置为True就好了,我们试下:
12frompypinyinimportpinyinprint(pinyin('朝阳',heteronym=True))运行结果:
1[['zhāo','cháo'],['yáng']]OK了,这下子就显示出来了两个读音了,而且我们也明白了结果为什么是一个二维列表,因为里面的一维的结果可能是多个,比如多音字的情况就是这样。但这个多少解析起来有点麻烦,很多情况下我们是不需要管多音字的,我们只是用它来转换一下名字而已,而处理上面的二维数组又比较麻烦。所以有没有一个方法直接给我们一个一维列表呢?有!我们可以使用lazy_pinyin这个方法来生成,尝试一下:
12frompypinyinimportlazy_pinyinprint(lazy_pinyin('聪明的小兔子'))运行结果:
1['cong','ming','de','xiao','tu','zi']这时候观察到得到的是一个列表,并且不再包含音调了。这里我们就有一个疑问了,为啥pinyin方法返回的结果默认是带音调的,而lazy_pinyin是不带的,这里面就涉及到一个风格转换的问题了。
我们可以对结果进行一些风格转换,比如不带声调风格、标准声调风格、声调在拼音之后、声调在韵母之后、注音风格等等,比如我们想要声调放在拼音后面,可以这么来实现:
1234frompypinyinimportlazy_pinyin,Stylestyle=Style.TONE3print(lazy_pinyin('聪明的小兔子',style=style))运行结果:
1['cong1','ming2','de','xiao3','tu4','zi']可以看到运行结果每个拼音后面就多了一个声调,这就是其中的一个风格,叫做TONE3,其实还有很多风格,下面是我从源码里面找出来的定义:
1defpinyin(hans,style=Style.TONE,heteronym=False,errors='default',strict=True)lazy_pinyin方法的定义如下:
1deflazy_pinyin(hans,style=Style.NORMAL,errors='default',strict=True)这下懂了吧,因为pinyin方法默认使用了TONE的风格,而lazy_pinyin方法默认使用了NORMAL的风格,所以就导致二者返回风格不同了。好了,有了这两个函数的定义,我们再来研究下其他的参数,比如定义里面的errors和strict参数又怎么用呢?
在这里我们先做一个测试,比如我们传入无法转拼音的字,比如:
12frompypinyinimportlazy_pinyinprint(lazy_pinyin('你好☆☆,我是xxx'))其中包含了星号两个,还有标点一个,另外还包含了一个xxx英文字符,结果会是什么呢?
1['ni','hao','☆☆,','wo','shi','xxx']可以看到结果中星号和英文字符都作为一个整体并原模原样返回了。那么这种特殊字符可以单独进行处理吗?当然可以,这里就用到刚才提到的errors参数了。errors参数是有几种模式的:
下面是errors这个参数的源码实现逻辑:
1234567891011121314def_handle_nopinyin_char(chars,errors='default'):"""处理没有拼音的字符"""ifcallable_check(errors):returnerrors(chars)iferrors=='default':returncharseliferrors=='ignore':returnNoneeliferrors=='replace':iflen(chars)>1:return''.join(text_type('%x'%ord(x))forxinchars)else:returntext_type('%x'%ord(chars))当处理没有拼音的字符的时候,errors的不同参数会有不同的处理结果,更详细的逻辑可以翻看源码。好了,下面我们来尝试一下,比如我们想将不能转拼音的字符去掉,则可以这么设置:
12frompypinyinimportlazy_pinyinprint(lazy_pinyin('你好☆☆,我是xxx',errors='ignore'))运行结果:
1['ni','hao','wo','shi']如果我们想要自定义处理,比如把☆转化为※,则可以这么设置:
1print(lazy_pinyin('你好☆☆,我是xxx',errors=lambdaitem:''.join(['※'ifc=='☆'elsecforcinitem])))运行结果:
如果对库返回的结果不满意,我们还可以自定义自己的拼音库,这里用到的方法就有load_single_dict和load_phrases_dict方法了。比如刚才我们看到“朝阳”两个字的发音默认返回的是zhaoyang,我们想默认返回chaoyang,那可以这么做:
12['zhao','yang']['chao','yang']这样就可以完成自定义的设置了。在一些项目里面我们可以自定义很多拼音库,然后加载就可以了。另外我们还可以注册样式实现自定义,比如将某个拼音前面加上Emoji表情,样例:
打开页面之后,我们可以看到页面的代码是一个字一个字敲出来的,这实际上是利用了一个定时器来实现的。首先我们可以预定义好所有的文本,然后动画播放的时候,首先把所有的文本隐藏,然后每隔几十毫秒读取一个字符,然后将其呈现出来。由于文本本身就是换行的,所以在呈现的时候就会一行一行地像打字机一样呈现出来。另外为了模拟打字的效果,在呈现的时候可以在最后的字符后面添加一个下划线符号,模拟打字的效果。其关键的实现代码如下:
123456#Youarethegreatestloveofmylife.whileTrue:ifu.with(i):you=everythingelse:everything=u这个代码的含义叫做“无论天涯海角,你都是我的一切。“,一个whileTrue循环代表了永久。这些代码其实都是在HTML代码中预定义好的,其中注释需要用span标签配以comments的class来修饰,缩进需要用span标签配以placeholder的class来修饰,例如:
123456
while
这里不同的格式用span的不同class来标识,空格缩进一个placeholder是两个空格,comments代表注释格式,关键词使用keyword来标识。如果你需要自定义自己的内容,通过控制这些内容穿插写入就好了。
对于域名解析,这个建议大家可以申请一个域名,比如我的域名就是cuiqingcai.com,我可以设置一个二级域名解析,叫做love.cuiqingcai.com。如果没有域名的话可以现买一个,比如阿里云、腾讯云购买,然后设置解析即可。如果没有域名,也可以使用一些虚拟云服务器,他们会帮你设置二级域名,当然也可以使用GitHubPages,甚至你使用IP地址来访问也是没问题的。
另外我还尝试过番茄土豆这个软件,这个软件的缺点在于整体的功能还比较简陋,而且不能和我已有的TodoList进行同步。好处就是可以自己设置番茄,保持专注工作。但目前我尚未发现满意的产品,如有还希望大家可以留言推荐一下,谢谢。
在学习的时候来进行记录是非常非常重要的,强烈建议一边学习一边把所做所想记录下来,最后做一下整理成文。一方面方便查阅,另一方面加深印象和理解。
言归正传,既然谈到笔记和写作。我的笔记本是Mac,之前几乎所有的笔记,包括写书,都几乎是在Mac上完成的,但是确实有的时候是不方便的。比如Mac不在身边或者想用iPhone或者iPad来写点东西的时候,一个需要解决的问题就是云同步问题。有了云同步,我们如果在电脑上写了一部分内容,接着切换了另一台台式机,或者切换了手机的时候,照样能够接着在原来的基础上写,非常方便。
对于电脑端的Markdown写作软件,推荐两款,一款是Typora,另一款是MarkEditor。
对于Makrdown编辑器来说,我觉得有几个比较重要的点:
以上介绍的这两款软件都可以做到。
不过目前由于Typora更轻量级,并且能和iPic而且功能配合使用,粘贴后的图片可以点击直接上传到云端,非常方便,我目前已经由MarkEditor切换到Typora了。
写作界面如下:
如图这是打开了一个文件夹,这个文件夹里面有好多Makrdown文件,都是我在研究和学习过程中所写的笔记。
然后需要解决的就是云同步问题,云同步其实使用网盘就足够了。由于我使用Mac,所以我选用了iCloud,开了200G的空间,足够了。这样我所记录的内容能够秒级同步到iCloudDrive中,这样我再使用iPhone、iPad就可以直接看到最新的内容了。当然还有一些推荐的,比如OneDrive、谷歌云等多种云盘同步工具,哪个方便用哪个。Mac和iPhone的好处就是已经内置了iCloudDrive,所以不用再去在各个终端上配置了。
接下来就是在其他的电脑以及iPhone、iPad上进行写作的解决方案了。由于我的文件都已经存放在了iCloudDrive,所以就需要一款Makrdown编辑软件可以直接读写iCloudDrive里面的内容,同时界面还要友好,功能完善一点。在这里我最终选择了MarkdownPro,它的功能简洁但是又比较完善,打开之后直接选取iCloudDrive里面的Makrdown文件即可开始编辑,并且它是左右分栏的,即左侧编辑,右侧预览,非常方便简洁,另外它对公式的支持也很好,下图是我在iPad上对本文进行编辑的效果预览图。
对于图片的插入,在iPhone和iPad上我借助了另外一个工具,叫做SM.MS,这个软件可以直接选取图片,然后上传到云端,点击复制即可得到链接和Makrdown图片链接,所以插图也不是什么问题了。
如图所示,上传照片之后,便会出现各种各样的图片链接形式,有纯链接、HTML、Markdown等等,直接点击复制按钮即可复制,然后粘贴到文档中。
另外如果你用了Windows的话,只要下载一个iCloud云盘软件即可同步。如果使用的是其他的云盘软件,也只需要配置一下就好了。
有了这套,我们就可以实现随时随地写笔记,Mac、iPhone、iPad无缝切换。
很多时候我们在构思方案或者流程的时候需要对思维做梳理,或者在列方案呈现的时候也需要分门别类地进行呈现。这时候大多数情况下就需要用到一个工具,思维导图。
思维导图工具我个人使用的是MindNode,在Mac上用它可以通过各种快捷键快速的增删思维导图节点,另外界面也非常绚丽多彩。
对于思维导图软件来说,我也希望能全平台同步,其实MindNode也有对应的移动端软件,同样是MindNode,二者可以通过iCloudDrive进行同步,同样可以做到无缝衔接。
另外还有很多朋友也在用XMind,功能同样很强大,大家也可以试试。
我们经常会和各种服务器打交道,例如我们经常使用SSH来远程连接某台Linux服务器,原生Terminal是支持SSH的,但你会发现原生带的这个太难用了。可能很多小伙伴使用iTerm,不得不说这确实是个神器,大大方便了远程管理流程。但我在这里还要推荐一个我经常使用的SSHShell,没错,它的名字就是SSHShell,它的页面操作简洁,同时管理和记录远程主机十分方便,另外还支持秘钥管理、自动重连、自定义主题等等功能,个人用起来十分顺手,强烈推荐!
当然除了电脑,当我们出去在外的时候,紧急情况也可能需要使用SSH来连接和管理我们的服务器,所以我也在iPhone和iPad上装了远程管理软件,叫做Termius,同样功能十分强大,快捷操作十分便捷,有免费的试用期限,我觉得非常好用就订购了,推荐给大家。
作为一名程序员,我们会经常写或者使用一些关键代码。
比如有一天我写了一些方法,这些方法可以完成非常重要的功能,后面的项目也会经常遇到,那么怎么办呢?很多情况下我们想把它保存起来,放到某个收藏夹里面备用,等到用的时候重新把它复制出来。或者有一些繁琐的命令,我实在是记不住,或许我们也想把它记录下来。
很多情况下,我们可能简单地使用文本文件,但并不方便同步和查找。或者云笔记保存下来,但这些并不是专门用来保存代码的。更高级一点,我们会联想到使用GitHubGists,但每次记录的这个流程也比较麻烦。
这里推荐一个专门用来记录代码片段的软件,叫做SnippetsLab,适用于Mac系统,可以专门用来管理代码片段,还支持多种代码格式。比如我就将代码按照编程语言划分,划分为Python、JavaScript等等,分文件夹存储,有不错的代码就随手贴过来,另外它也支持搜索,管理代码非常方便。如果某一天想查某个代码了,直接打开它一搜就有了,可以大大提高开发效率。
另外再问下大家,你们买iPad了吗?是不是觉得比较鸡肋,或者平时都用不上,那这样就没有发挥iPad的最大效用,如果利用好了,它可以进一步方便我们的生活,后面我也会专门写一下iPad方面的一些用途。
由于水平和见识有限,如果大家有更好的软件或者方案推荐,欢迎大家留言!也希望我的一些方案对大家有所帮助,谢谢!
另外还有一些特点就不再一一赘述了,这其中包含了区块位置、区块大小、区块标签、区块内容、区块疏密度等等多种特征,另外很多情况下还需要借助于视觉的特征,所以说这里面其实结合了算法计算、视觉处理、自然语言处理等各个方面的内容。如果能把这些特征综合运用起来,再经过大量的数据训练,是可以得到一个非常不错的效果的。
未来的话,页面也会越来越多,页面的渲染方式也会发生很大的变化,爬虫也会越来越难做,智能化爬虫也将会变得越来越重要。目前工业界,其实已经有落地的算法应用了。经过我的一番调研,目前发现有这么几种算法或者服务对页面的智能化解析做的比较好:
Service/Software
Diffbot
0.968
0.978
0.971
0.893
0.924
0.819
0.911
0.854
0.876
0.892
0.850
0.786
0.880
0.822
0.498
0.815
0.608
另外它还有几个可选参数:
以上的预定字段就是如果可以返回那就会返回的字段,是不能定制化配置的,另外我们还可以通过fields参数来指定扩展如下可选字段:
好,以上便是这个API的用法,大家可以申请之后使用这个API来做智能化解析了。下面我们用一个实例来看一下这个API的用法,代码如下:
哇!感觉很爽的样子今天我们就来实现一个类似的代理!其实SoEasy!我们需要借助公有云来实现。下面我以AWS举例(其它公有云操作类似,唯一的区别的就是:各个服务的名字不同而已)
以上完毕!你可以不停的重启Ec2实例!你就有百万IP池啦!!(前提是你有钱啊)下面是重启Ec2的示例:
下面我们将从各个方面分析一下各个套餐的优劣。
通过可用率统计,我们可以发现可用率较高的代理套餐有:
通过平均响应速度判别,我们可以发现响应速度较快的代理套餐有:
通过平均响应速度方差分析,我们可以发现稳定性较高的代理套餐有:
我们可以看一下各个套餐的价格:
对于安全性,此处主要考虑提取API是否有访问验证,使用代理时是否有访问验证,即可以通过设置白名单来控制哪些可以使用。其中只有芝麻HTTP代理、太阳HTTP代理默认使用了白名单限制,即只有将使用IP添加到白名单才可以使用,可以有效控制使用权限。另外阿布云代理提供了隧道代理验证,只有成功配置了用户名和密码才可以正常使用。所以在此归纳如下:
不同的接口具有不同的API调用频率限制,归纳如下:
在此可以简单总结如下:
级别
除了常规的测试之外,我这边还选取了某些套餐的与众不同之处进行说明,这些特点有的算是缺点,有的算是优点,现列举如下:
分项了解了各个代理套餐的可用率、响应速度、稳定性、性价比、安全性等内容之后,最后做一下总结:
所以在综合来看比较推荐的有:芝麻代理、快代理、讯代理、阿布云、多贝云代理,详细的对比结果可以参照表格。以上便是各家代理的详细对比测评情况,希望此文能够在大家选购代理的时候有所帮助。
其中第一列的文件权限信息是非常重要的,它由十个字符组成:
我们可以使用chmod命令来改变文件或目录的权限,有这么几种用法。一种是数字权限命名,rwx对应一个二进制数字,如101就代表拥有读取和执行的权限,而转为十进制的话,r就代表4,w就代表2,x就代表1,然后三个数字加起来就和二进制数字对应起来了。如7=4+2+1,这就对应着rwx;5=4+1,这就对应着r-x。所以,相应地777就代表了rwxrwxrwx,即所有者、所属用户组、其他用户对该文件都拥有读取、写入、执行的权限,这是相当危险的!赋予权限的命令如下:
1sudochmod
1sudochmod777file.txt另外我们也可以使用代号来赋予权限,代号有u、g、o、a四中,分别代表所有者权限,用户组权限,其他用户权限和所有用户权限,这些代号后面通过+和-符号来控制权限的添加和移除,再后面跟上权限类型就好,例如:
1sudochmodu-xfile.txt就是给所有者移除x权限,也就是执行权限。
1sudochmodg+wfile.txt就是为用户组添加w权限,即写入权限。另外如果是文件夹的话还可以对文件夹进行递归赋权限操作,如:
1sudochmod-R777share就是将share文件夹和其内所有内容都赋予777权限。好,有了权限的标识,那我们还得把用户和用户组与文件关联起来啊,这里使用的命令就是chown和chgrp命令。命令格式如下:
12sudochown
1sudochowncqcfile.txt如果我要将file.txt所属用户组换成lab,那就可以使用如下命令:
1sudochgrplabfile.txt另外同样可以使用-R来进行递归操作,如将share文件夹及其内所有内容的所有者都换成cqc,命令如下:
1sudochown-Rcqcshare/好,了解了chown、chgrp、chmod之后,我们就可以灵活地对文件权限进行控制了。
首先我先为自己创建一个账户,添加一个cqc的用户:
1sudoaddusercqc运行之后会提示输入密码和其他信息:
12345678910111213141516Addinguser`cqc'...Addingnewgroup`cqc'(1002)...Addingnewuser`cqc'(1002)withgroup`cqc'...Creatinghomedirectory`/home/cqc'...Copyingfilesfrom`/etc/skel'...EnternewUNIXpassword:RetypenewUNIXpassword:passwd:passwordupdatedsuccessfullyChangingtheuserinformationforcqcEnterthenewvalue,orpressENTERforthedefaultFullName[]:RoomNumber[]:WorkPhone[]:HomePhone[]:Other[]:Istheinformationcorrect[Y/n]这时候发现一个同名的组就被创建了,查看下cqc所在的组:
1groupscqc结果如下:
1cqc:cqc再用id命令查看下信息:
1idcqc结果如下:
1uid=1002(cqc)gid=1002(cqc)groups=1002(cqc)可以看到当前cqc只属于cqc用户组。接下来我们创建一个用户组,叫做lab,来标明我的实验室,命令如下:
1sudogroupaddlab然后查看下用户组里面的成员:
1memberslab没有任何结果,说明我们创建了一个空的组,没有任何成员。然后我们将刚才创建的cqc加入到该组中,因为我自己也属于该实验室,肯定也要加进来,命令如下:
1sudoaddusercqclab结果:
123Addinguser`cqc'togroup`lab'...AddingusercqctogrouplabDone.然后查看下组内成员:
1memberslab结果:
1cqc这样lab组内就有了cqc这个用户了。别忘了cqc还需要拥有root权限,所以我们还需要将cqc添加到sudo组内,命令如下:
1sudoaddusercqcsudo结果:
123Addinguser`cqc'togroup`sudo'...AddingusercqctogroupsudoDone.这样就成功加入到sudo组了,cqc也就是我的账户就可以使用sudo命令了。查看下用户状态:
1uid=1002(cqc)gid=1002(cqc)groups=1002(cqc),27(sudo),1003(lab)这样cqc就属于三个用户组了,既是实验室成员,又拥有root权限。上面的分配用户组的命令我们也可以使用usermod来实现:
1sudousermod-aGsudo,labcqc这样就添加到多个组了。
接下来,再添加实验室的另外一个人员lbd,然后将其添加到lab组中,流程是类似的,命令如下:
12sudoadduserlbdsudoadduserlbdlab运行完毕之后,id命令查看其信息:
1idlbd结果如下:
1uid=1004(lbd)gid=1005(lbd)groups=1005(lbd),1003(lab)这样就成功创建lbd,并将其添加到实验室lab组了。
最后另外添加一个用户slb,非实验室成员,只创建账户就好了,命令如下:
1sudoadduserslb但是我们不把他加入lab组中。查看他的状态:
1idslb结果如下:
1uid=1003(slb)gid=1004(slb)groups=1004(slb)所以三人的状态是这样的:
12cd/srvsudomkdirshare注意这里我还是使用ubuntu账户来创建的。先看下当前目录权限:
1ls-l结果如下:
1234total12drwxr-xr-x3rootroot4096Sep418:17./drwxr-xr-x24rootroot4096Sep418:17../drwxr-xr-x2rootroot4096Sep418:17share/可以看到share文件的所有者是root,用户组也是root,权限是755,即只有root拥有修改权限,其他的只有读取和执行权限。然后进入share文件夹创建一个names.txt:
12cdsharesudovinames.txt编辑内容如下:
12cqclbd保存完毕之后,这时查看一下文件权限,如下:
12vinames.txtcatnames.txt为什么呢?因为该文件是刚才由ubuntu账号使用sudo命令创建的,因此文件的所有者是root,并不是cqc,因此即使文件的权限是640,那也就不能使用文件所有者的权限,而且cqc也不属于root组,所以也不能使用文件组的权限了,因此什么都看不了,什么都改不了。但cqc属于sudo组啊,可以利用sudo命令临时获取root权限,临时以root的身份来操作该文件,这样就可以来查看和修改文件了,因此下面的命令是有效的:
12sudovinames.txtsudocatnames.txt但这样还是需要使用sudo才能修改,很不方便。这时如果我们把文件的所有者改成cqc,情况那就不一样了。使用ubuntu账号,对names.txt更改其所有者为cqc,改的命令如下:
1sudochowncqcnames.txt这时查看下文件信息:
1-rw-r-----1cqcroot8Sep420:29names.txt可以看到所有者信息已经变成了cqc,这样cqc账号再直接查看和修改,那就可以了,不再需要sudo命令:
1-rw-r-----1cqcroot8Sep420:31names.txtlbd不是所有者了,因此前面的rw-权限是没什么用的,但他属于lab组,而该文件对于用户组的权限是r—,也就是读取权限。我们使用lbd账号来尝试看下文件的内容:
12catnames.txtcat:names.txt:Permissiondenied很遗憾,又没有权限。因为什么?因为这个文件的用户组并不是lab啊,而lbd这个用户又不属于root组,所以没有任何权限。那咋办?将文件的用户组改成lab就好了,使用ubuntu账号或cqc账号来操作:
1sudochgrplabnames.txt这样就成功将文件所属用户组改成lab了,接下来再使用lbd账号查看下文件内容:
1catnames.txt就成功读取了。然而lbd现在是没有写入权限的,因为对于用户组来说,该文件的权限是r—,如果要获取写入权限,我们可以使用如下命令:
1sudochmodg+wnames.txt或:
1sudochmod660names.txt这样就相当于赋予了rw-权限,下面我们再使用lbd账号尝试修改这个文件:
12catnames.txtvinames.txt均无权限。所以说,这样我们就成功为实验室的人员赋予了权限,而非实验室的人则没有任何权限。如果我要为slb赋予读取权限咋办呢?很简单,添加一下就好了:
1sudochmodo+rnames.txt这就是为其他用户添加了读取权限。这时slb就可以读取文件,但不能修改文件,也是比较安全的。好,如果我的文件非常多呢?比如十几二十个,都放在share文件夹内,那不能一个个进行权限设置吧?这时候我们只需要针对文件夹进行操作即可,下面的命令就可以为share文件夹赋予775权限,即所有者cqc和所在组lab可对其进行查看和修改,其他的人只能看不能改:
123sudochmod-R775share/sudochown-Rcqcshare/sudochgrp-Rlabshare/注意文件夹一般都会赋予x权限,不然连进入文件夹的权限都没有。这也就是文件夹一般会赋予775、755,而文件会赋予664、600、644、640的原因了。赋予775权限之后,share的权限就变成了:
1drwxrwxr-x2cqclab4096Sep420:31share/这样其他用户就只能看,不能改,这样普通文件就没什么问题了。如文件夹内包含了可执行文件,还可以单独为其他用户针对可执行文件去除x权限,如去除Python文件的可执行权限:
1sudochmodo-x*.py好了,到现在为止,我们就得心应手地完成了权限控制了!相信如果你耐心看完的话,什么用户管理、权限管理,都不在话下!
如果你只有一台主机的话,其实索引的健康状况也是yellow,因为一台主机,集群没有其他的主机可以防止副本,所以说,这就是一个不健康的状态,因此集群也是十分有必要的。
另外,既然是群集,那么存储空间肯定也是联合起来的,假如一台主机的存储空间是固定的,那么集群它相对于单个主机也有更多的存储空间,可存储的数据量也更大。所以综上所述,我们需要一个集群!
接下来我们再来了解下集群的结构是怎样的。首先我们应该清楚多台主机构成了一个集群,每台主机称作一个节点(Node)。如图就是一个三节点的集群:
在图中,每个Node都有三个分片,其中P开头的代表Primary分片,即主分片,R开头的代表Replica分片,即副本分片。所以图中主分片1、2,副本分片0储存在1号节点,副本分片0、1、2储存在2号节点,主分片0和副本分片1、2储存在3号节点,一共是3个主分片和6个副本分片。同时我们还注意到1号节点还有个MASTER的标识,这代表它是一个主节点,它相比其他的节点更加特殊,它有权限控制整个集群,比如资源的分配、节点的修改等等。这里就引出了一个概念就是节点的类型,我们可以将节点分为这么四个类型:
以上就是节点几种类型,一个节点其实可以对应不同的类型,如一个节点可以同时成为主节点和数据节点和预处理节点,但如果一个节点既不是主节点也不是数据节点,那么它就是负载均衡节点。具体的类型可以通过具体的配置文件来设置。
好,接下来我们就来动手搭建一个集群吧。这里我一共拥有七台Linux主机,系统是Ubuntu16.04,都连接在一个内网中,IP地址为:
内容
主机台数
7
主机内存
13GB
主机系统
Ubuntu16.04
存储空间
1TB
Elasticsearch版本
6.3.2
Java版本
1.8
Kibana版本
Elasticsearch是基于Lucene的,而Lucene又是基于Java的。所以第一步我们就需要在每台主机上安装Java。首先更新Apt源:
1sudoapt-getupdate然后安装Java:
1sudoapt-getinstalldefault-jre安装好了之后可以检查下Java的版本:
1java-version这里的版本是1.8,类似输出如下:
123openjdkversion"1.8.0_171"OpenJDKRuntimeEnvironment(build1.8.0_171-8u171-b11-0ubuntu0.16.04.1-b11)OpenJDK64-BitServerVM(build25.171-b11,mixedmode)如果看到上面的内容就说明安装成功了。注意一定要每台主机都要安装。
接下来我们来安装Elasticsearch,同样是每台主机都需要安装。首先需要添加Apt-key:
12sudoapt-getupdatesudoapt-getinstallelasticsearch运行完毕之后我们就完成了Elasticsearch的安装,注意还是要每台主机都要安装。
这时我们只是每台主机都安装好了Elasticsearch,接下来我们还需要将它们联系在一起构成一个集群。安装完之后,Elasticsearch的配置文件是/etc/elasticsearch/elasticsearch.yml,接下来让我们编辑一下配置文件:
通过cluster.name可以配置集群的名称,集群是一个整体,因此名称都要一致,所有主机都配置成相同的名称,配置示例:
1cluster.name:germey-es-clusters通过node.name可以配置每个节点的名称,每个节点都是集群的一部分,每个节点名称都不要相同,可以按照顺序编号,配置示例:
1node.name:es-node-1其他的主机可以配置为es-node-2、es-node-3等。
通过node.master可以配置该节点是否有资格成为主节点,如果配置为true,则主机有资格成为主节点,配置为false则主机就不会成为主节点,可以去当数据节点或负载均衡节点。注意这里是有资格成为主节点,不是一定会成为主节点,主节点需要集群经过选举产生。这里我配置所有主机都可以成为主节点,因此都配置为true,配置示例:node.master:true
通过node.data可以配置该节点是否为数据节点,如果配置为true,则主机就会作为数据节点,注意主节点也可以作为数据节点,当node.master和node.data均为false,则该主机会作为负载均衡节点。这里我配置所有主机都是数据节点,因此都配置为true,配置示例:node.data:true
通过path.data和path.logs可以配置Elasticsearch的数据存储路径和日志存储路径,可以指定任意位置,这里我指定存储到1T硬盘对应的路径下,另外注意一下写入权限问题,配置示例:path.data:/datadrive/elasticsearch/datapath.logs:/datadrive/elasticsearch/logs
我们需要设定Elasticsearch运行绑定的Host,默认是无法公开访问的,如果设置为主机的公网IP或0.0.0.0就是可以公开访问的,这里我们可以都设置为公开访问或者部分主机公开访问,如果是公开访问就配置为:
通过discovery.zen.ping.unicast.hosts可以配置集群的主机地址,配置之后集群的主机之间可以自动发现,这里我配置的是内网地址,配置示例:
1discovery.zen.ping.unicast.hosts:["10.0.0.4","10.0.0.5","10.0.0.6","10.0.0.7","10.0.0.8","10.0.0.9","10.0.0.10"]这里请改成你的主机对应的IP地址。
为了防止集群发生“脑裂”,即一个集群分裂成多个,通常需要配置集群最少主节点数目,通常为(可成为主节点的主机数目/2)+1,例如我这边可以成为主节点的主机数目为7,那么结果就是4,配置示例:
1discovery.zen.minimum_master_nodes:4另外还可以配置当最少几个节点回复之后,集群就正常工作,这里我设置为4,可以酌情修改,配置示例:
1gateway.recover_after_nodes:4其他的暂时先不需要配置,保存即可。注意每台主机都需要配置。
配置完成之后就可以在每台主机上分别启动Elasticsearch服务了,命令如下:
123sudosystemctldaemon-reloadsudosystemctlenableelasticsearch.servicesudosystemctlstartelasticsearch.service所有主机都启动之后,我们在任意主机上就可以查看到集群状态了,命令行如下:
接下来我们需要安装一个Kibana来帮助可视化管理Elasticsearch,依然还是通过Apt安装,只需要任意一台主机安装即可,因为集群是一体的,所以Kibana在任意一台主机只要能连接到Elasticsearch即可,安装命令如下:
1sudoapt-getinstallkibana安装之后修改/etc/kibana/kibana.yml,设置公开访问和绑定的端口:
12server.port:5601server.host:"0.0.0.0"然后启动服务:
这样Kibana可视化管理就配置成功了。
现在集群已经初步搭建完成了,但是现在集群很危险,如果我们配置了可公网访问,那么它是可以被任何人操作的,比如储存数据,增删节点等,这是非常危险的,所以我们必须要设置访问权限。在Elasticsearch中,配置认证是通过X-Pack插件实现的,幸运的是,我们不需要额外安装了,在Elasticsearch6.3.2版本中,该插件是默认集成到Elasticsearch中的,所以我们只需要更改一部分设置就可以了。首先我们需要升级License,只有修改了高级版License才能使用X-Pack的权限认证功能。在Kibana中访问Management->Elasticsearch->LicenseManagement,点击右侧的升级License按钮,可以免费试用30天的高级License,升级完成之后页面会显示如下:
1xpack.security.enabled:true随后设置elastic、kibana、logstash_system三个用户的密码,任意一台主机修改之后,一台修改,多台生效,命令如下:
1/usr/share/elasticsearch/bin/elasticsearch-setup-passwordsinteractive运行之后会依次提示设置这三个用户的密码并确认,一共需要输入六次密码,完成之后就成功设置好了密码了。修改完成之后重启Elasticsearch和Kibana服务:
例如这里添加一个超级管理员的账户:
1bootstrap.memory_lock:true但这样修改之后重新启动是会报错的,Elasticsearch无法正常启动,查看日志,报错如下:
12[1]bootstrapchecksfailed[1]:memorylockingrequestedforelasticsearchprocessbutmemoryisnotlocked这里需要修改两个地方,第一个是/etc/security/limits.conf,添加如下内容:
123456*softnofile65536*hardnofile65536*softnproc32000*hardnproc32000*hardmemlockunlimited*softmemlockunlimited另外还需要修改/etc/systemd/system.conf,修改如下内容:
1sudosystemctlrestartelasticsearch.service重新访问刚才的地址,即可发现每台主机的物理地址锁定都被打开了:
另外还推荐安装中文分词插件,这样可以对中文进行全文索引,安装命令如下: