分类
python

和你一起终身学习【Python基础教程】(python入门教程小白不要错过)

Python 是一种解释型、面向对象、动态数据类型的高级程序设计语言。

Python 由 Guido van Rossum 于 1989 年底发明,第一个公开发行版发行于 1991 年。

像 Perl 语言一样, Python 源代码同样遵循 GPL(GNU General Public License) 协议。

官方宣布,2020 年 1 月 1 日, 停止 Python 2 的更新。

Python 2.7 被确定为最后一个 Python 2.x 版本。

谁适合阅读本教程?

本教程适合想从零开始学习 Python 编程语言的开发人员。当然本教程也会对一些模块进行深入,让你更好的了解 Python 的应用。

本教程主要针对 Python 2.x 版本的学习,如果你使用的是 Python 3.x 版本请移步至Python 3.X 版本的教程。

本教程所有实例基于 Python2.7。

学习本教程前你需要了解

在继续本教程之前,你应该了解一些基本的计算机编程术语。如果你学习过 PHP,ASP 等编程语言,将有助于你更快的了解 Python 编程。

执行Python程序

对于大多数程序语言,第一个入门编程代码便是 "Hello World!",以下代码为使用 Python 输出 "Hello World!":

实例

#!/usr/bin/python print("Hello, World!")

Python 3.0+ 版本已经把 print 作为一个内置函数,输出 "Hello World!" 代码如下:

实例(Python 3.0+)

#!/usr/bin/python3 print("Hello, World!")

一篇笔记

关于脚本第一行的 #!/usr/bin/python 的解释,相信很多不熟悉 Linux 系统的同学需要普及这个知识,脚本语言的第一行,只对 Linux/Unix 用户适用,用来指定本脚本用什么解释器来执行。

有这句的,加上执行权限后,可以直接用 ./ 执行,不然会出错,因为找不到 python 解释器。

#!/usr/bin/python 是告诉操作系统执行这个脚本的时候,调用 /usr/bin 下的 python 解释器。

#!/usr/bin/env python 这种用法是为了防止操作系统用户没有将 python 装在默认的 /usr/bin 路径里。当系统看到这一行的时候,首先会到 env 设置里查找 python 的安装路径,再调用对应路径下的解释器程序完成操作。

#!/usr/bin/python 相当于写死了 python 路径。

#!/usr/bin/env python 会去环境设置寻找 python 目录,可以增强代码的可移植性,推荐这种写法。

分成两种情况:

(1)如果调用 python 脚本时,使用:

python script.py 

#!/usr/bin/python 被忽略,等同于注释

(2)如果调用python脚本时,使用:

./script.py 

#!/usr/bin/python 指定解释器的路径

PS:shell 脚本中在第一行也有类似的声明。

分类
python

Python3入门–基础(合肥python3入门教程)

Python3入门之一

Python标识符大小写敏感。

基本数据类型:

int类型:

Python所能表示的整数大小只受限于机器内存,而非固定数量的字节数。

str类型

字符串可以使用双引号或单引号封装-只要字符串头尾使用的符号是对称的。 Python使用方括号[]来存取字符串等序列中的某一项,索引位置是从0开始计数的,例如"giraffe"[0]。字符就是指长度为1的字符串。

str类型与基本的数值类型(比如int)都是固定的-一旦设定,其值就不能改变。 这句大意是指什么?

组合数据类型:

元组

可用于存储任意数量、任意类型的数据项。元组是固定的,创建之后就不能改变。

元组使用逗号创建

>>> "Denmark","Finland","Norway","Sweden"

('Denmark','Finland','Norway','Sweden')

>>>"one",

('one',)

在输出元组时,Python使用圆括号使其封装在一起。很多程序员模仿这种机制,总是将自己定义的元组常值包括在圆括号中。如果某个元组只有一个数据项,又需要使用圆括号,就仍然必须使用逗号,比如(1,)。空源组则使用空的()创建。逗号还可以用于在函数调用时分隔参数,因此,如果需要将元组常值作为参数传递,就必须使用括号对其进行封装。

列表

可用于存储任意数量、任意类型的数据项。列表是可变的,可以插入或移除数据项。

列表与元组实质上并不是真正存储数据项,而是存放对象引用。创建列表与元组时(以及在列表中插入数据项时),实际上是使用其给定的对象引用的副本。在字面意义项(比如整数或字符串)的情况下,会在内存中创建适当数据类型的对象,而存放在列表或元组中的才是对象引用。该句什么意思?

与Python中的其他内容一样,组合数据类型也是对象,因此,可以将某种组合数据类型嵌套在其他组合数据类型中,比如,创建一个列表,其中的每个元素也是列表。列表、元组以及大多数Python的其他组合数据类型存储的是对象引用,而非对象本身。

如果需要将一个数据项从某种类型转换为另一种类型,那么可以使用语法datatype(item),如int("45")。

type()函数可以返回给定数据项的数据类型

len()函数以某个单独的数据项作为参考,并返回该数据项的“长度”(int类型)。

>>>len(("one",))

1

>>>len([3, 5, 1, 2, "pause", 5])

6

>>>len("automatically")

13

元组、列表以及字符串等数据类型是“有大小的”,也就是说,对这些数据类型而言,长度或大小等度量是有意义的,将这些数据类型的数据项作为参数传递给len()函数是有意义的。

对象引用

对固定对象(比如intS与strS),变量与对象引用之间没有可察觉的差别。

对可变对象,则存在差别。

固定对象与可变对象?

举例:

x = "blue" #创建一个str对象,其文本内容为"blue"。同时创建一个名为x的对象引用,x引用的就是这个str对象。

y = "green"

z = x #创建一个名为z的新对象引用,并将其设置为对象引用x所指向的相同对象。

在Python中, "="的作用是将对象引用于内存中的某对象进行绑定。如果对象 引用已经存在,就简单地进行重绑定,以便引用"="操作符右面的对象;如果对象引用尚未存在,就由"="操作符创建对象引用。

print(x,y,z) # prints: blue green blue

z = y

print(x,y,z) # prints: blue green green

x = z

print(x,y,z) # prints: green green green 所有3个对象引用实际上引用的都是同一个str。由于不存在更多的对字符串"blue"的对象引用,因此Python可以对其进行垃圾收集处理。

这种机制称为动态类型机制,即某对象引用可以重新绑定到不同类型的对象,但是只允许执行与某种特定类型绑定的操作。

[]操作符可以用于序列,(元组、列表都是序列)

逻辑操作符

身份操作符:is操作符是一个二元操作符,如果其左端的对象引用与右端的对象引用指向的是同一个对象,则会返回true。

实例:

>>> a = ["Retention", 3, None]

>>> b = ["Retention", 3, None]

>>> a is b

False

对intS、strS以及很多其他数据类型进行比较是没有意义的。实际上,使用is对数据项进行比较可能会导致直觉外的结果。虽然a和b在最初设置为同样的列表值,但是列表本身是以单独的list对象存储的,因此,在第一次使用时,is操作符将返回false。身份比较并不必须对进行比较的对象本身进行检查,is操作符只需要对对象所在的内存地址进行比较-同样的地址存储的是同样的对象。

使用is将数据项与内置的空对象None进行比较,None通常用作位置标记值,指示“未知”或“不存在”:(is not是对身份测试的反向测试)

>>> a = "Something"

>>> b = None

>>> a is not None, b is None

(True,True)

身份操作符的作用是查看两个对象引用是否指向相同的对象,或查看某个对象是否为None。

成员操作符

对序列或集合这一类数据类型,比如字符串、列表或元组,我们可以使用操作符in来测试成员关系,用not in来测试非成员关系。

>>> p = (4,"frog",9,-33,9,2)

>>> 2 in p

True

>>> "dog" not in p

True

>>> phrase = "Wild Swans by Jung Chang"

>>> "J" in phrase #字符是长度为1的字符串

True

对列表与元组,in操作符使用线性搜索,对非常大的组合类型,速度可能会较慢,而对字典或集合,in操作可以非常快。

比较操作符

结构比较

>>> a = 9

>>> 0 <= a <= 10

True

对给定数据项取值在某个范围内的情况,这种测试方式提供了很多便利,不需要使用逻辑运算符and进行两次单独的比较操作。只需要对数据项进行一次评估(因为数据项在表达式中只出现一次),如果数据项值得计算需要耗费大量时间或存取数据项会带来一些副作用,这种优势会更加明显。

逻辑运算符

and、or与not。and与or都使用short-circuit逻辑,并返回决定结果的操作数,如果逻辑表达式本身出现在布尔上下文中,那么结果也为布尔值。not总是返回布尔型结果。

>>> five = 5

>>> two = 2

>>> zero = 0

>>> five and two

2

>>> two and five

5

>>> five and zero

0

这个特性好好学习一下

增强的赋值操作符,比如+=或*=

第一点:int数据类型是固定的-一旦赋值,就不能改变。对固定的对象使用增强的赋值操作符时,实际上是创建一个对象来存储结果,之后,目标对象引用重新绑定,

Python重载了操作符+和+=,将其分别用于字符串与列表,前者表示连接,后者表示追加字符串并扩展(追加另一个字符串)列表:

>>>name = "John"

>>>name + "Doe"

'JohnDoe'

>>>name+="Doe"

>>>name

'John Doe'

>>> seeds = ["sesame","sunflower"]

>>> seeds += ["pumpkin"]

>>> seeds

["sesame","sunflower", "pumpkin"]

列表+=操作符右边的操作数必须是一个iterable,如果不是,就会产生意外

>>> seeds += 5 #错误

>>> seeds += [5]

>>> seeds

["sesame","sunflower", "pumpkin", 5]

>>> seeds += "durian"

>>> seeds

["sesame","sunflower", "pumpkin", 5, "d","u","r","i"."a","n"]

列表的+=操作符会扩展列表,并将给定的iterable中的每一项追加到列表后。由于字符串是一个iterable,这会导致字符串中的每个字符被单独添加。如果使用append,那么该参数总是以单独的项目添加。

控制流语句

布尔表达式实际上就是对某对象进行布尔运算,并可以产生一个布尔值结果(True或False)。在Python中,预定义为常量False的布尔表达式、特殊对象None、空序列或集合(比如,空字符串、列表或元组)、值为0的数值型数据项等的布尔结果为False,其他的则为True。创建自定义数据类型时,我们可以自己决定这些自定义数据类型在布尔上下文中的返回值。

在Python中,一块代码,也就是说一条或多条语句组成的序列,称为suite。由于Python中的某些语法要求存在一个suite,Python就提供了关键字pass,pass实际上是一条空语句,不进行任何操作,可以用在需要suite但又不需要进行处理的地方。

for…in语句

Python的for循环语句重用了关键字in(在其他上下文中,in是一个成员操作符),并使用如下的语法格式:

for variable in iterable:

suite

所有Python数据项都是某种特定数据类型(也称之为“类”)的“对象”(也称之为“实例”),我们将交替地使用术语“数据类型”与“类”。对象与有些其他语言(比如C++或Java的内置数值类型)提供的数据项的关键区别在于,对象可以有“方法”。实质上,简单地说,方法就是某特定对象可以调用的函数。比如,数据类型list有一个append()方法,借助于该方法,可以以如下方式添加对象:

>>> x = ["zebra", 49, -879, "aardvark", 200]

>>> x.append("more")

>>> x

['zebra', 49, -879, 'aardvark', 200, 'more']

对象x知道自身是一个list(所有Python对象都知道自身的数据类型),因此,不需要明确地指定数据类型。在append()方法的实现中,第一个参数是x对象本身-这是由Python自动完成的(作为其对方法的句法支持的一部分)。append()方法会改变原始的列表。这是可以实现的,因为列表这种数据类型本身就是可变的。与创建新列表(使用原始的数据项以及额外要添加的数据项)、之后重新绑定对新列表的对象引用相比,append()方法具有潜在的更高的效率,对于很长的列表尤其如此。

>>> list.append(x, "extra")

>>> x

['zebra', 49, -879,'aardvark', 200,'more','extra']

这里,我们指定了数据类型以及该数据类型的方法,并将要调用该方法的数据项本身作为第一个参数,其后跟随其他一些参数。

常规的函数调用方式functionName(arguments) 方法调用方式objectName.methodName(arguments)

点(存取属性)操作符用于存取对象的某个属性。由于属性可以是对象,该对象包含某些属性,这些属性又可以包含其他属性,依此类推,可以根据需要使用多级嵌套的点操作符来存取特定的属性。

基本的异常处理

python的很多函数与方法都会产生异常,并将其作为发生错误或重要事件的标志。与Python的其他对象一样,异常也是一个对象,转换为字符串(比如:打印)异常会产生一条消息文本。

异常处理的简单语法格式:

try:

try_suite

except exception1 as variable1:

exception_suite1

except exceptionN as variableN:

exception_suiteN

异常处理以如下的逻辑工作:如果try块中的suite都正常执行,而没有产生异常,则except模块将被跳过;如果try块中产生了异常,则控制流会立即转向第一个与该异常匹配的suite-这意味着,跟随在产生异常的语句后面的suite中的语句将不再执行;如果发生了异常,并且给定了as variable部分,则在异常处理suite内部,variable引用的是异常对象。如果异常发生在处理except块时,或者某个异常不能与任何一个except块匹配,Python就会在下一个封闭范围内搜索一个匹配的except块。对合适的异常处理模块的搜索是向外扩展的,并可以延展到调用栈内,直到发现一个匹配的异常处理模块,或者找不到匹配的模块,这种情况,程序将终止,并留下一个未处理的异常,此时,Python会打印回溯信息以及异常的消息文本。 这里描述的不懂

Python3程序开发指南

分类
python

Python知识点之Python爬虫(python爬虫入门知识讲解)

1.scrapy框架有哪几个组件/模块?

Scrapy Engine: 这是引擎,负责Spiders、ItemPipeline、Downloader、Scheduler中间的通讯,信号、数据传递等等!(像不像人的身体?)

Scheduler(调度器): 它负责接受引擎发送过来的requests请求,并按照一定的方式进行整理排列,入队、并等待Scrapy Engine(引擎)来请求时,交给引擎。

Downloader(下载器):负责下载Scrapy Engine(引擎)发送的所有Requests请求,并将其获取到的Responses交还给Scrapy Engine(引擎),由引擎交给Spiders来处理,

Spiders:它负责处理所有Responses,从中分析提取数据,获取Item字段需要的数据,并将需要跟进的URL提交给引擎,再次进入Scheduler(调度器),

Item Pipeline:它负责处理Spiders中获取到的Item,并进行处理,比如去重,持久化存储(存数据库,写入文件,总之就是保存数据用的)

Downloader Middlewares(下载中间件):你可以当作是一个可以自定义扩展下载功能的组件

2.简单说一下scrapy工作流程。

数据在整个Scrapy的流向:

程序运行的时候,

引擎:Hi!Spider, 你要处理哪一个网站?

Spiders:我要处理23wx.com

引擎:你把第一个需要的处理的URL给我吧。

Spiders:给你第一个URL是XXXXXXX.com

引擎:Hi!调度器,我这有request你帮我排序入队一下。

调度器:好的,正在处理你等一下。

引擎:Hi!调度器,把你处理好的request给我,

调度器:给你,这是我处理好的request

引擎:Hi!下载器,你按照下载中间件的设置帮我下载一下这个request

下载器:好的!给你,这是下载好的东西。(如果失败:不好意思,这个request下载失败,然后引擎告诉调度器,这个request下载失败了,你记录一下,我们待会儿再下载。)

引擎:Hi!Spiders,这是下载好的东西,并且已经按照Spider中间件处理过了,你处理一下(注意!这儿responses默认是交给def parse这个函数处理的)

Spiders:(处理完毕数据之后对于需要跟进的URL),Hi!引擎,这是我需要跟进的URL,将它的responses交给函数 def xxxx(self, responses)处理。还有这是我获取到的Item。

引擎:Hi !Item Pipeline 我这儿有个item你帮我处理一下!调度器!这是我需要的URL你帮我处理下。然后从第四步开始循环,直到获取到你需要的信息,注意!只有当调度器中不存在任何request了,整个程序才会停止,(也就是说,对于下载失败的URL,Scrapy会重新下载。)

3.scrapy指纹去重原理和scrappy-redis的去重原理?

scrapy的去重原理流程:利用hash值和集合去重。首先创建fingerprint = set()结合,然后将request对象利用sha1对象进行信息摘要,摘要完成之后, 判断hash值是否在集合中,如果在,返回true,如果不在,就add到集合。

scrapy-redis 的去重原理基本是一样的,只不过持久化存储到redis共享数据库中,当请求数据达到10亿级以上,这个时候就会非常消耗内存,一个sha1 40个字节,就会占40G的内存,这个存储绝大部分的数据库无法承受,这个时候就要使用布隆过滤器。

4.请简要介绍下scrapy框架。

scrapy 是一个快速(fast)、高层次(high-level)的基于 python 的 web 爬虫构架,用于抓取web站点并从页面中提取结构化的数据。scrapy 使用了 Twisted异步网络库来处理网络通讯。

5.为什么要使用scrapy框架?scrapy框架有哪些优点?

它更容易构建大规模的抓取项目

它异步处理请求,速度非常快

它可以使用自动调节机制自动调整爬行速度

6.scrapy如何实现分布式抓取?

可以借助scrapy_redis类库来实现。

在分布式爬取时,会有master机器和slave机器,其中,master为核心服务器,slave为具体的爬虫服务器。

我们在master服务器上搭建一个redis数据库,并将要抓取的url存放到redis数据库中,所有的slave爬虫服务器在抓取的时候从redis数据库中去链接,由于scrapy_redis自身的队列机制,slave获取的url不会相互冲突,然后抓取的结果最后都存储到数据库中。master的redis数据库中还会将抓取过的url的指纹存储起来,用来去重。相关代码在dupefilter.py文件中的request_seen()方法中可以找到。

去重问题:

dupefilter.py 里面的源码:

def request_seen(self, request):

fp = request_fingerprint(request)

added = self.server.sadd(self.key, fp)

return not added

去重是把 request 的 fingerprint 存在 redis 上,来实现的。

7.scrapy和requests的使用情况?

requests 是 polling 方式的,会被网络阻塞,不适合爬取大量数据

scapy 底层是异步框架 twisted ,并发是最大优势

8.爬虫使用多线程好?还是多进程好?为什么?

对于IO密集型代码(文件处理,网络爬虫),多线程能够有效提升效率(单线程下有IO操作会进行IO等待,会造成不必要的时间等待,而开启多线程后,A线程等待时,会自动切换到线程B,可以不浪费CPU的资源,从而提升程序执行效率)。

在实际的采集过程中,既考虑网速和相应的问题,也需要考虑自身机器硬件的情况,来设置多进程或者多线程。

9.了解哪些基于爬虫相关的模块?

网络请求:urllib,requests,aiohttp

数据解析:re,xpath,bs4,pyquery

selenium

js逆向:pyexcJs

10.列举在爬虫过程中遇到的哪些比较难的反爬机制?

动态加载的数据

动态变化的请求参数

js加密

代理

cookie

11.简述如何抓取动态加载数据?

基于抓包工具进行全局搜索

如果动态加载的数据是密文,则全局搜索是搜索不到

12.移动端数据如何抓取?

fiddler,appnium,网络配置

13.如何实现全站数据爬取?

基于手动请求发送+递归解析

基于CrwalSpider(LinkExtractor,Rule)

14.如何提升爬取数据的效率?

使用框架

线程池,多任务的异步协程

分布式

15.列举你接触的反爬机制?

从功能上来讲,爬虫一般分为数据采集,处理,储存三个部分。这里我们只讨论数据采集部分。

一般网站从三个方面反爬虫:用户请求的Headers,用户行为,网站目录和数据加载方式。前两种比较容易遇到,大多数网站都从这些角度来反爬虫。第三种一些应用ajax的网站会采用,这样增大了爬取的难度。

1)通过Headers反爬虫

从用户请求的Headers反爬虫是最常见的反爬虫策略。很多网站都会对Headers的User-Agent进行检测,还有一部分网站会对Referer进行检测(一些资源网站的防盗链就是检测Referer)。如果遇到了这类反爬虫机制,可以直接在爬虫中添加Headers,将浏览器的User-Agent复制到爬虫的Headers中;或者将Referer值修改为目标网站域名。对于检测Headers的反爬虫,在爬虫中修改或者添加Headers就能很好的绕过。

2)基于用户行为反爬虫

还有一部分网站是通过检测用户行为,例如同一IP短时间内多次访问同一页面,或者同一账户短时间内多次进行相同操作。大多数网站都是前一种情况,对于这种情况,使用IP代理就可以解决。可以专门写一个爬虫,爬取网上公开的代理ip,检测后全部保存起来。这样的代理ip爬虫经常会用到,最好自己准备一个。有了大量代理ip后可以每请求几次更换一个ip,这在requests或者urllib2中很容易做到,这样就能很容易的绕过第一种反爬虫。

对于第二种情况,可以在每次请求后随机间隔几秒再进行下一次请求。有些有逻辑漏洞的网站,可以通过请求几次,退出登录,重新登录,继续请求来绕过同一账号短时间内不能多次进行相同请求的限制。

3)动态页面的反爬虫

上述的几种情况大多都是出现在静态页面,还有一部分网站,我们需要爬取的数据是通过ajax请求得到,或者通过JavaScript生成的。首先用Firebug或者HttpFox对网络请求进行分析。

如果能够找到ajax请求,也能分析出具体的参数和响应的具体含义,我们就能采用上面的方法,直接利用requests或者urllib2模拟ajax请求,对响应的json进行分析得到需要的数据。

能够直接模拟ajax请求获取数据固然是极好的,但是有些网站把ajax请求的所有参数全部加密了。我们根本没办法构造自己所需要的数据的请求。我这几天爬的那个网站就是这样,除了加密ajax参数,它还把一些基本的功能都封装了,全部都是在调用自己的接口,而接口参数都是加密的。遇到这样的网站,我们就不能用上面的方法了,我用的是selenium+phantomJS框架,调用浏览器内核,并利用phantomJS执行js来模拟人为操作以及触发页面中的js脚本。从填写表单到点击按钮再到滚动页面,全部都可以模拟,不考虑具体的请求和响应过程,只是完完整整的把人浏览页面获取数据的过程模拟一遍。

用这套框架几乎能绕过大多数的反爬虫,因为它不是在伪装成浏览器来获取数据(上述的通过添加 Headers一定程度上就是为了伪装成浏览器),它本身就是浏览器,phantomJS就是一个没有界面的浏览器,只是操控这个浏览器的不是人。利用 selenium+phantomJS能干很多事情,例如识别点触式(12306)或者滑动式的验证码,对页面表单进行暴力破解等等。它在自动化渗透中还 会大展身手,以后还会提到这个。

16.什么是深度优先和广度优先(优劣)

默认情况下scrapy是深度优先。

深度优先:占用空间大,但是运行速度快。

广度优先:占用空间少,运行速度慢

17.是否了解谷歌的无头浏览器?

无头浏览器即headless browser,是一种没有界面的浏览器。既然是浏览器那么浏览器该有的东西它都应该有,只是看不到界面而已。

Python中selenium模块中的PhantomJS即为无界面浏览器(无头浏览器):是基于QtWebkit的无头浏览器。

18.说下Scrapy的优缺点。

优点:

scrapy 是异步的

采取可读性更强的xpath代替正则

强大的统计和log系统

同时在不同的url上爬行

支持shell方式,方便独立调试

写middleware,方便写一些统一的过滤器

通过管道的方式存入数据库

缺点:基于python的爬虫框架,扩展性比较差

基于twisted框架,运行中的exception是不会干掉reactor,并且异步框架出错后是不会停掉其他任务的,数据出错后难以察觉。

19.需要登录的网页,如何解决同时限制ip,cookie,session?

解决限制IP可以使用代理IP地址池、服务器;不适用动态爬取的情况下可以使用反编译JS文件获取相应的文件,或者换用其它平台(比如手机端)看看是否可以获取相应的json文件。

20.验证码的解决?

1.输入式验证码

解决思路:这种是最简单的一种,只要识别出里面的内容,然后填入到输入框中即可。这种识别技术叫OCR,这里我们推荐使用Python的第三方库,tesserocr。对于没有什么背影影响的验证码,直接通过这个库来识别就可以。但是对于有嘈杂的背景的验证码这种,直接识别识别率会很低,遇到这种我们就得需要先处理一下图片,先对图片进行灰度化,然后再进行二值化,再去识别,这样识别率会大大提高。

验证码识别大概步骤:

转化成灰度图

去背景噪声

图片分割

2.滑动式验证码

解决思路:对于这种验证码就比较复杂一点,但也是有相应的办法。我们直接想到的就是模拟人去拖动验证码的行为,点击按钮,然后看到了缺口 的位置,最后把拼图拖到缺口位置处完成验证。

第一步:点击按钮。然后我们发现,在你没有点击按钮的时候那个缺口和拼图是没有出现的,点击后才出现,这为我们找到缺口的位置提供了灵感。

第二步:拖到缺口位置。我们知道拼图应该拖到缺口处,但是这个距离如果用数值来表示?通过我们第一步观察到的现象,我们可以找到缺口的位置。这里我们可以比较两张图的像素,设置一个基准值,如果某个位置的差值超过了基准值,那我们就找到了这两张图片不一样的位置,当然我们是从那块拼图的右侧开始并且从左到右,找到第一个不一样的位置时就结束,这是的位置应该是缺口的left,所以我们使用selenium拖到这个位置即可。这里还有个疑问就是如何能自动的保存这两张图?这里我们可以先找到这个标签,然后获取它的location和size,然后 top,bottom,left,right = location['y'] ,location['y']+size['height']+location['x'] + size['width'] ,然后截图,最后抠图填入这四个位置就行。具体的使用可以查看selenium文档,点击按钮前抠张图,点击后再抠张图。最后拖动的时候要需要模拟人的行为,先加速然后减速。因为这种验证码有行为特征检测,人是不可能做到一直匀速的,否则它就判定为是机器在拖动,这样就无法通过验证了。

3.点击式的图文验证 和 图标选择

图文验证:通过文字提醒用户点击图中相同字的位置进行验证。

图标选择: 给出一组图片,按要求点击其中一张或者多张。借用万物识别的难度阻挡机器。

这两种原理相似,只不过是一个是给出文字,点击图片中的文字,一个是给出图片,点出内容相同的图片。

这两种没有特别好的方法,只能借助第三方识别接口来识别出相同的内容,推荐一个超级鹰,把验证码发过去,会返回相应的点击坐标。然后再使用selenium模拟点击即可。具体怎么获取图片和上面方法一样。

21.滑动验证码如何破解?

破解核心思路:

1、如何确定滑块滑动的距离?

滑块滑动的距离,需要检测验证码图片的缺口位置

滑动距离 = 终点坐标 – 起点坐标

然后问题转化为我们需要屏幕截图,根据selenium中的position方法并进行一些坐标计算,获取我们需要的位置

2、坐标我们如何获取?

起点坐标:

每次运行程序,位置固定不变,滑块左边界离验证码图片左边界有6px的距离

终点坐标:

每次运行程序,位置会变,我们需要计算每次缺口的位置

怎么计算终点也就是缺口的位置?

先举个例子,比如我下面两个图片都是120×60的图片,一个是纯色的图片,一个是有一个蓝色线条的图片(蓝色线条位置我事先设定的是60px位置),我现在让你通过程序确定蓝色线条的位置,你怎么确定?

答案:

遍历所有像素点色值,找出色值不一样的点的位置来确定蓝色线条的位置

这句话该怎么理解?大家点开我下面的图片,是不是发现图片都是由一个一个像素点组成的,120×60的图片,对应的像素就是横轴有120个像素点,纵轴有60个像素点,我们需要遍历两个图片的坐标并对比色值,从(0,0)(0,1)……一直到(120,60),开始对比两个图片的色值,遇到色值不一样的,我们return返回该位置即可

22.爬下来的数据是怎么存储?

以json格式存储到文本文件

这是最简单,最方便,最使用的存储方式,json格式保证你在打开文件时,可以直观的检查所存储的数据,一条数据存储一行,这种方式适用于爬取数据量比较小的情况,后续的读取分析也是很方便的。

存储到excel

如果爬取的数据很容易被整理成表格的形式,那么存储到excel是一个比较不错的选择,打开excel后,对数据的观察更加方便,excel也可以做一些简单的操作,写excel可以使用xlwt这个库,读取excel可以使用xlrd,同方法1一样,存储到excel里的数据不宜过多,此外,如果你是多线程爬取,不可能用多线程去写excel,这是一个限制。

存储到sqlite

sqlite无需安装,是零配置数据库,这一点相比于mysql要轻便太多了,语法方面,只要你会mysql,操作sqlite就没有问题。当爬虫数据量很大时,需要持久化存储,而你又懒得安装mysql时,sqlite绝对是最佳选择,不多呢,它不支持多进程读写,因此不适合多进程爬虫。

存储到mysql数据库

mysql可以远程访问,而sqlite不可以,这意味着你可以将数据存储到远程服务器主机上,当数据量非常大时,自然要选择mysql而不是sqlite,但不论是mysql还是sqlite,存储数据前都要先建表,根据要抓取的数据结构和内容,定义字段,这是一个需要耐心和精力的事情

存储到mongodb

我最喜欢no sql 数据库的一个原因就在于不需要像关系型数据库那样去定义表结构,因为定义表结构很麻烦啊,要确定字段的类型,varchar 类型数据还要定义长度,你定义的小了,数据太长就会截断。

mongodb 以文档方式存储数据,你使用pymongo这个库,可以直接将数据以json格式写入mongodb, 即便是同一个collection,对数据的格式也是没有要求的,实在是太灵活了。

刚刚抓下来的数据,通常需要二次清洗才能使用,如果你用关系型数据库存储数据,第一次就需要定义好表结构,清洗以后,恐怕还需要定义个表结构,将清洗后的数据重新存储,这样过于繁琐,使用mongodb,免去了反复定义表结构的过程。

23.cookie过期的处理问题?

这时候就需要cookie自动的更新了。通常怎样自动更新cookie呢?这里会用到selenium。

步骤1、 采用selenium自动登录获取cookie,保存到文件;

步骤2、 读取cookie,比较cookie的有效期,若过期则再次执行步骤1;

步骤3、 在请求其他网页时,填入cookie,实现登录状态的保持。

24.Selenium和PhantomJS

selenium

Selenium是一个用于Web应用程序测试的工具。Selenium测试直接运行在浏览器中,就像真正的用户在操作一样。支持的浏览器包括IE(7, 8, 9, 10, 11),Mozilla Firefox,Safari,Google Chrome,Opera等主流浏览器。这个工具的主要功能包括:测试与浏览器的兼容性

——测试你的应用程序看是否能够很好得工作在不同浏览器和操作系统之上。

它的功能有:

框架底层使用JavaScript模拟真实用户对浏览器进行操作。测试脚本执行时,浏览器自动按照脚本代码做出点击,输入,打开,验证等操作,就像真实用户所做的一样,从终端用户的角度测试应用程序。

使浏览器兼容性测试自动化成为可能,尽管在不同的浏览器上依然有细微的差别。

使用简单,可使用Java,Python等多种语言编写用例脚本

也就是说,它可以根据指令,做出像真实的人在访问浏览器一样的动作,比如打开网页,截图等功能。

phantomjs

(新版本的selenium已经开始弃用phantomjs, 不过有时候我们可以单独用它做一些事情)是一个基于Webkit的无界面浏览器,可以把网站内容加载到内存中并执行页面上的各种脚本(比如js)。

25.怎么判断网站是否更新?

1、304页面http状态码

当第二次请求页面访问的时候,该页面如果未更新,则会反馈一个304代码,而搜索引擎也会利用这个304http状态码来进行判断页面是否更新。

首先第一次肯定是要爬取网页的,假设是A.html,这个网页存储在磁盘上,相应地有个修改时间(也即是更新这个文件的时间)。

那么第二次爬取的时候,如果发现这个网页本地已经有了,例如A.html,这个时候,你只需要向服务器发送一个If-Modified-Since的请求,把A.html的修改时间带上去。

如果这段时间内,A.html更新了,也就是A.html过期了,服务器就会HTTP状态码200,并且把新的文件发送过来,这时候只要更新A.html即可。

如果这段时间内,A.html的内容没有变,服务器就会返返回HTTP状态码304(不返回文件内容),这个时候就不需要更新文件。

2、Last-Modified文件最后修改时间

这是http头部信息中的一个属性,主要是记录页面最后一次的修改时间,往往我们会发现,一些权重很高的网站,及时页面内容不更新,但是快照却还是能够每日更新,这其中就有Last-Modified的作用。通产情况下,下载网页我们使用HTTP协议,向服务器发送HEAD请求,可以得到页面的最后修改时间LastModifed,或者标签ETag。将这两个变量和上次下载记录的值的比较就可以知道一个网页是否跟新。这个策略对于静态网页是有效的。是对于绝大多数动态网页如ASP,JSP来说,LastModifed就是服务器发送Response的时间,并非网页的最后跟新时间,而Etag通常为空值。所以对于动态网页使用LastModifed和Etag来判断是不合适的,因此Last-Modified只是蜘蛛判断页面是否更新的一个参考值,而不是条件。

分类
python

python列表生成式用法(python列表解析式的用法)

列表生成式:List Comprehensions,是python中非常非常简单但却最常用的一个功能。

根据名字就能知道,列表生成式所返回的应该是list类型,它能用最简单最易懂的写法生成所需要的列表。

例:我需要得到1-100这个list的所有数的平方所组成的一个list。此时可以用for循环:

Python代码

a = []

for value in range(1, 101):

a.append(value * value)

print(a)

此时得到的a就是1-100中每个数的平方所组成的数组。这个方法很简单,但是使用列表生成式更简单。

Python代码

a = [value * value for value in range(1,101)]

print(a)

得到的a与上一个方法中的a一摸一样。

在 a = [value * value for value in range(1,101)] 中,value * value是一个表达式,value这个数来源于表达式后面的for循环,for循环每循环一次,就计算一次表达式,最后将for循环中每次循环的数的计算结果都保存在一个列表中。最后赋值给a。

在列表生成式中,也可以使用多次循环。比如:

Python代码

a = [x * y for x in range(1,3) for y in range(3,5)]

print(a)

生成结果为:

终端代码

[3, 4, 6, 8]

range(1,3)为[1, 2], range(3,5)为[3,4],x来自range(1,3),y来自range(3,5)

结果为:1*3, 1*4, 2*3, 2*4

此外,还可以在列表生成式中增加条件判断:

Python代码

a = [value * value for value in range(1, 11) if value % 2 == 0]

print(a)

#结果为:

[4, 16, 36, 64, 100]

在for循环后面给value值增加条件选择。此例子是计算1-10中偶数的平方

更多技巧请《转发 + 关注》哦!

分类
python

小智的魔法书-python世界里的列表(三)

又是一个明媚的星期天,据天气预报说今天有雨,趁着雨还没来,栽植了紫薯、辣椒、茄子,今天3点多到5点半,弓着腰,体会了两个多小时的面朝黑土背朝天。想着今天6点要做核酸,起得太早了。趁静默期来python这个小世界修炼一会儿,等静默时间过了,再去种些土豆和黄瓜。

核酸基本上已经常态化了,频率是三天一次,离他国疫区太近了,这几天,附近的城市的感染人数有增加,也导致那些城市的静默期时间变长,估计有些人要错过农作物的种植。还是小世界里舒服,这是一片没有病毒的净土。

列表数据和字符串数据的序号规则一样,从左向右数是从0开始的正整数,从右向左数是从-1开始的负整数,可使用“盒子名称[序号]”来单独使用某元素,不同的地方是,当我们使用列表、字符串的某一元素的时候,字符串的元素不可以被赋值,而列表元素可被赋值。

>>> a='142857'
>>> a[0]
'1'
>>> a[0]='5'
TypeError: 'str' object does not support item assignment
>>> a
'142857'
>>>
>>> b=list(a)
>>> b
['1', '4', '2', '8', '5', '7']
>>> b[0]
'1'
>>> b[0]='5'
>>> b
['5', '4', '2', '8', '5', '7']
>>>

值得注意的是:上面的例子呢,当通过序号来使用内元素的时候,一定要注意区分盒子的类型,因为a[0]、b[0]写法太相似了,赋值的行为又很普遍,很容易混淆从而产生赋值错误。

如何规避混淆的产生呢,有几种方法:

第一种方法:把字符串仅当成单独一个整体使用,一定要更改字符串里某个元素时,将盛装字符串的盒子重新赋值。

>>> a='142857'
>>> a
'142857'
>>> a[0]
'1'
>>> a='542857' #盒子a重新赋值
>>> a
'542857'
>>> a[0]
'5'
>>>

盒子a重新复制后,与原来的a是不同的。

第二种方法:使用list()工具,将字符串转换成列表后再赋值,虽然不产生赋值错误,但是当使用join()工具后,产生的字符串有可能不是当初的字符串了。

>>> a='142857'
>>> b=list(a)
>>> b
['1', '4', '2', '8', '5', '7']
>>> b[0]
'1'
>>> b[0]='5'
>>> b
['5', '4', '2', '8', '5', '7']
>>> c=''.join(b)
>>> c
'542857'
>>> a == c
False
>>>

这里不能用a.split(),这种方法不能将a字符串里的元素拆成列表里的元素。

第三种:当遇到多个元素时,不确定是否有赋值行为,那么采用列表数据的格式,以保障赋值不出错误提示。

>>> a='142857'
>>> a=list(a)
>>> a
['1', '4', '2', '8', '5', '7']
>>> a[0]='9'
>>> a
['9', '4', '2', '8', '5', '7']
>>>

书写列表时,引号与用于间隔的逗号一个一个写将是很闹心的,使用字符串转换为列表的工具很方便很快捷。

第四种:使用替换工具replace(),replace()工具的括号内必须有两个内容,两个内容之间要用逗号进行间隔,replace(old,new),将现有的某元素(old)替换成另一个元素(new),这个工具还包含一个替换的次数,不写的话,是全部替换,使用格式是“字符串.replace()”,注意括号里要写两个内容。

>>> a='12123'
>>> a.replace('1','6')
'62623'
>>> a.replace('1','6',10) #带次数10
'62623'
>>> a
'12123'
>>>

这个例子中,'1'元素有两个,第一个不带次数的replace()进行了全部替换,第二个replace()工具带了次数10,10>2,也是全部替换。

>>> b=a.replace('1','6')
>>> b
'62623'
>>> a
'12123'
>>>

这个replace()工具不能将原来的字符串覆盖掉,只能在原来的基础上产生一个新的字符串,若需要保留这个新的字符串,那么需要给它一个盒子。

replace()工具除了对单个字符有用,还可以对某个连续的字符进行替换,比方对“12”进行替换。这个留着当作业吧。

replace()工具不能直接用于列表数据,

>>> b=list(a)
>>> b
['1', '2', '1', '2', '3']
>>> b.replace('1','6')
AttributeError: 'list' object has no attribute 'replace'
>>>

列表数据不能使用replace来替换内元素。

>>> b[0]='9'
>>> b
['9', '2', '1', '2', '3']
>>>

列表数据里的元素替换目前最直接的做法是赋值。

加上前篇的两个作业,现在有三个作业了。作业还蛮多哒,哈哈哈。

呀,静默期已经过了,去种土豆和黄瓜啦,下回见。

——————————-

分类
python

python列表推导式和生成器表达式(python推导表达式)

列表推导式和生成器表达式以及字典推导式通常被视为Python中函数式编程的一部分,列表推导允许您使用包含较少代码的for循环创建列表。

列表推导式

用[] 包围

ll = [i for i in range(5)]
print(ll)  # [0, 1, 2, 3, 4]
ll = [i for i in range(5) if i % 2 == 0]
print(ll)  # [0, 2, 4]
ll = [[i, j] for i, j in enumerate(["a", "b", "c", "d"])]
print(ll)  # [[0, 'a'], [1, 'b'], [2, 'c'], [3, 'd']]

字典推导式

用{} 包围

dd = {i: j for i, j in enumerate(["a", "b", "c"])}
print(dd)  # {0: 'a', 1: 'b', 2: 'c'}
dd = {k: v for k, v in {1: 1, 2: 2, 3: 3, 4: 4}.items() if k % 2 == 0}
print(dd)  # {2: 2, 4: 4}

生成器表达式

生成器表达式允许在没有yield关键字的情况下即时创建生成器。

语法和概念类似于列表推导的语法和概念:用()包围

gg = (i for i in range(5))
print(gg)
print(next(gg))
print(gg.__next__())
"""
<generator object <genexpr> at 0x7f922e0992d0>
0
1
"""
gg = (i for i in range(5))
print(gg)  # <generator object <genexpr> at 0x7ffbec2992d0>
for i in gg:
    print(i)
"""
<generator object <genexpr> at 0x7ffbec2992d0>
0
1
2
3
4
"""

分类
python

超强汇总:学习Python列表,只需这篇文章就够了

千里之行,始于足下。要练成一双洞悉一切的眼睛,还是得先把基本功扎扎实实地学好。今天,本喵带大家仔细温习一下Python的列表。温故而知新,不亦说乎。

当然,温习的同时也要发散思考,因为有些看似无关紧要的、约定俗成的语言习惯,例如数组索引为何从0开始,其背后可能大有来历。知其然,亦需知其所以然啊喵喵喵~~~

最后,在基础知识之上,更要探索进阶,例如学习生成器表达式,这样既能更扎实地掌握基础,又能融会贯通,获得更全面的认知升级。

Python的列表是怎样滴?

列表(list)是一种有序的集合,可以随时添加、查找和删除元素。

列表支持加入不同数据类型的元素:数字、字符串、列表、元组等。

列表通过有序的索引可遍历所有的元素,从前往后数,索引是[0,n-1],从后往前数,索引是[-1, -n],其中n是列表的长度。

列表可以是不含元素的空列表,也可以包含超级多的元素(在内存大小支持的情况下)。

 list_a = [] # 空列表,即len(list_a) == 0
 list_b = [2018, 10, '2018-10-1', ['hi', 1, 2], (33, 44)]
 # list_b 长度为5,包含2个数字元素、1个字符串元素、1个列表元素和1个元组元素
 len(list_b) == 5
 list_b[0] == list_b[-5] == 2018
 lits_b[3] == list_b[-2] == ['hi', 1, 2] 
 lits_b[4] == list_b[-1] == (33, 44)

Python中怎么操作列表?

1)创建列表:

用中括号[]包裹元素,元素使用逗号分隔。

用list()方法,转化生成列表。

列表生成式/列表解析式/列表推导式,生成列表。

 list_a = [1, 2, 3]
 list_b = list("abc") # list_b == ['a', 'b', 'c']
 list_c = list((4, 5, 6)) # list_c == [4, 5, 6]
 list_d = [i for i in list_a] # list_d == [1, 2, 3]
 list_e = [i*j for i in list_a for j in list_c] # list_e == [4,5,6,10,12,12,15,18]
 list_f = [i*j for i,j in zip(list_a,list_c)] # list_f == [4, 10, 18]
 list_g = [i for i in list_a if i%2 == 0] # list_g == [2]
 ?
 # 结合range()函数,range(start, stop[, step])
 list_h = list(range(3)) # list_h == [0, 1, 2]
 list_i = list(range(3,7)) # list_i == [3, 4, 5, 6]
 list_j = list(range(3,9,2)) # list_j == [3, 5, 7]
 ?
 # 找出100以内的能够被3整除的正整数
 list_k = list(range(3,100,3)) # list_k == [3, 6, 9, ..., 96, 99]

2)扩充列表:

用append()方法,在列表尾部添加单个新元素。

用insert()方法,在列表中指定位置添加元素。

用 “+” 运算符,将两个列表拼接出一个新列表。

用extend()方法,在一个列表后面拼接进另一个列表。

 
 # 以下分别添加2个元素
 list_a = []
 list_a.append('happy') # list_a == ['happy']
 list_a.insert(0, 'very') # list_a == ['very', 'happy']
 ?
 # 以下两种扩充列表方式
 list_1 = ['I', 'am']
 list_2 = ['very', 'happy']
 list_3 = list_1 + list_2 # 新列表 list_3 == ['I', 'am', 'very', 'happy']
 list_1.extend(list_2) # 原列表1扩充,list_1 == ['I', 'am', 'very', 'happy']

3)删减列表与销毁列表:

用del list[m] 语句,删除指定索引m处的元素。

用remove()方法,删除指定值的元素(第一个匹配项)。

用pop()方法,取出并删除列表末尾的单个元素。

用pop(m)方法,取出并删除索引值为m的元素。

用clear()方法,清空列表的元素。(杯子还在,水倒空了)

用del list 语句,销毁整个列表。(杯子和水都没有了)

 
 # 以下4种删除列表元素方式
 list_1 = list_2 = list_3 = list_4 = ['I', 'am', 'very', 'happy']
 del list_1[0] # list_1 == ['am', 'very', 'happy']
 list_2.remove('I') # list_2 == ['am', 'very', 'happy']
 list_3.pop() # list_3 == ['I', 'am', 'very']
 list_4.pop(0) # list_4 == ['am', 'very', 'happy']
 ?
 # 清空与销毁
 list_a = [1, 2, 3]
 list_b = [1, 2, 3]
 list_b.clear() # list_b == []
 del list_a # 没有list_a了,再使用则会报错

4)列表切片:

基本含义:从第i位索引起,向右取到后n位元素为止,按m间隔过滤

基本格式:[i : i+n : m] ;i 是切片的起始索引值,为列表首位时可省略;i+n 是切片的结束位置,为列表末位时可省略;m 可以不提供,默认值是1,不允许为0,当m为负数时,列表翻转。注意:这些值都可以大于列表长度,不会报越界。

 
 li = [1, 4, 5, 6, 7, 9, 11, 14, 16]
 ?
 # 以下写法都可以表示整个列表,其中 X >= len(li)
 li[0:X] == li[0:] == li[:X] == li[:] == li[::] == li[-X:X] == li[-X:]
 ?
 li[1:5] == [4,5,6,7] # 从1起,取5-1位元素
 li[1:5:2] == [4,6] # 从1起,取5-1位元素,按2间隔过滤
 li[-1:] == [16] # 取倒数第一个元素
 li[-4:-2] == [9, 11] # 从倒数第四起,取-2-(-4)=2位元素
 li[:-2] == li[-len(li):-2] == [1,4,5,6,7,9,11] # 从头开始,取-2-(-len(li))=7位元素
 ?
 # 注意列表先翻转,再截取
 li[::-1] == [16,14,11,9,7,6,5,4,1] # 翻转整个列表
 li[::-2] == [16,11,7,5,1] # 翻转整个列表,再按2间隔过滤
 li[:-5:-1] == [16,14,11,9] # 翻转整个列表,取-5-(-len(li))=4位元素
 li[:-5:-3] == [16,9] # 翻转整个列表,取-5-(-len(li))=4位元素,再按3间隔过滤
 ?
 li[::0] # 报错(ValueError: slice step cannot be zero)

5) 其它操作:

用len()方法,统计全部元素的个数。

用count()方法,统计指定值的元素的个数。

用max()方法,统计元素中的最大值(要求元素类型相同;数字类型直接比较,其它类型比较id)

用min()方法,统计元素中的最小值(要求元素类型相同;数字类型直接比较,其它类型比较id)

用index()方法,查找指定值的元素的索引位置(第一个匹配项)。

用reverse()方法,翻转列表中的元素。

用copy()方法,浅拷贝并生成新的列表。

用deepcopy()方法,深拷贝并生成新的列表。

用sort()方法,在原列表基础上进行排序。

用sorted()方法,将新列表基础上对原列表的元素进行排序。

 
 list_1 = [2018, 10, '2018-10-1', ['hi', 1, 2], (33, 44)]
 len(list_1) == 5
 list_1.count(10) == 1 # 元素10的数量为1
 list_1.index(10) == 1 # 元素10的索引为1
 list_1.reverse() # list_1 == [(33, 44), ['hi', 1, 2], '2018-10-1', 10, 2018]
 ?
 ?
 # 比较浅拷贝与深拷贝
 import copy
 list_a = [2018, 10, '2018-10-1', ['hi', 1, 2], (33, 44)]
 list_b = ['hi', 1, 2]
 list_c = list_a.copy() # list_c == [2018, 10, '2018-10-1', ['hi', 1, 2], (33, 44)]
 list_d = copy.deepcopy(list_a) # list_d == [2018, 10, '2018-10-1', ['hi', 1, 2], (33, 44)]
 # 改变原列表中的可变对象元素
 list_a[3].append('new') # list_a == [2018, 10, '2018-10-1', ['hi', 1, 2, 'new'], (33, 44)]
 # 浅拷贝中的可变对象会随原列表变化而变化
 list_c == [2018, 10, '2018-10-1', ['hi', 1, 2, 'new'], (33, 44)]
 # 深拷贝中的可变对象不会随原列表变化而变化
 list_d == [2018, 10, '2018-10-1', ['hi', 1, 2], (33, 44)]
 ?
 ?
 # 比较sort() 与 sorted()
 list_1 = list_2 = [2,1,4,6,5,3]
 list_1.sort() # 原列表变化:list_1 == [1,2,3,4,5,6]
 list_3 = sorted(list_2) # 原列表不变:list_2 == [2,1,4,6,5,3]; list_3 == [1,2,3,4,5,6]

Python列表索引为何从0始?

权威解释来自Guido van Rossum(Python之父)的博文:《Why Python uses 0-based indexing》

一句话总结:索引从0开始,切片用法很优雅。

翻译精华如下:

我决定在Python中使用0-based索引方式的一个原因,就是切片语法(slice notation)。

让我们来先看看切片的用法。可能最常见的用法,就是“取前n位元素”或“从第i位索引起,取后n位元素”(前一种用法,实际上是i==起始位的特殊用法)。如果这两种用法实现时可以不在表达式中出现难看的+1或-1,那将会非常的优雅。

使用0-based的索引方式、半开区间切片和缺省匹配区间的话(Python最终采用这样的方式),上面两种情形的切片语法就变得非常漂亮:a[:n]和a[i:i+n],前者是a[0:n]的缩略写法。

如果使用1-based的索引方式,那么,想让a[:n]表达“取前n个元素”的意思,你要么使用闭合区间切片语法,要么在切片语法中使用切片起始位和切片长度作为切片参数。半开区间切片语法如果和1-based的索引方式结合起来,则会变得不优雅。而使用闭合区间切片语法的话,为了从第i位索引开始取后n个元素,你就得把表达式写成a[i:i+n-1]。

……

特别是当两个切片操作位置邻接时,第一个切片操作的终点索引值是第二个切片的起点索引值时,太漂亮了,无法舍弃。例如,你想将一个字符串以i,j两个位置切成三部分,这三部分的表达式将会是a[:i],a[i:j]和a[j:]。

其它编程语言的索引?

索引从0开始的编程语言:C、C++、Python、Java、PHP、Ruby、Javascript…

索引从1开始的编程语言:ABC、Matlab、VB、易语言、大部分shell语言…

索引从其它值开始的编程语言:Pascal、Lua…

还有像表示星期、月份等序列结构的数据,各种编程语言也划分成了不同阵营。

它们出于何种考虑?

C语言:索引从0开始,可以大大提升内存寻址计算的效率,详细分析参考《C语言数组元素下标为何从0开始》

大部分shell语言:大多数是从1开始,来源参考stackexchange这篇问答

Pascal、Lua:默认从1开始,但支持改变起始索引值,原因据说是对非专业的开发者更友好,来源参考这篇知乎问答

以上列举的原因是最审慎的、体面的解释,话题应该到此终结,因为“索引应该从几开始最好”这个问题的破坏性不亚于“哪种编程语言是最好的”……

优雅漂亮的结尾:生成器表达式

列表生成式是一种漂亮优雅的东西,然而它有一个致命的缺点:它一次性把所有元素加载到内存中,当列表过长的时候,便会占据过多的内存资源,而且,我们通常仅需要使用少数的元素,这样未使用的元素所占据的绝大部分的内存,就成了不必要的支出。

生成器是一种更高级更优雅的东西,它使用“懒加载”的原理,并不生成完整的列表,而是迭代地、即时地、按需地生成元素,这样不仅能极大地节省内存空间,而且,在理论上,它可以生成一个无穷大的列表!

大多数生成器是以函数来实现的,然而,它并不返回(return)一个值,而是生成(yield)一个值,并挂起程序。然后,通过next()方法生成并马上返回一个元素,或者通过for循环,逐一生成和返回全部元素。

next()效率太低,且调用次数越界时会抛出StopIteration的异常,而for循环会自动捕捉这个异常,并停止调用,所以使用更佳。

 
 # 计算斐波那契数列的生成器
 def fibon(n):
 a = b = 1
 for i in range(n):
 yield a # 使用yield
 a, b = b, a + b
 ?
 # 计算前1000000个数,通过next()函数,按顺序每次生成一个数
 g = fibon(1000000)
 next(g) # 1
 next(g) # 1
 next(g) # 2
 next(g) # 3
 next(g) # 5
 # 以此类推,但若调用超过1000000次,就会报异常StopIteration
 ?
 # 计算前1000000个数,通过for循环逐一打印生成数
 for x in fibon(1000000):
 print(x)

生成器表达式与列表生成式极其形似,只是把[]改成了(),但背后的原理大不相同。

 
 l = [x*2 for x in range(5)] # 列表生成式,4以内整数的2倍数
 g = (x*2 for x in range(5)) # 生成器表达式
 type(l) # 结果:<type 'list'>
 type(g) # 结果:<type 'generator'>
 ?
 print(l) # 结果:[0,2,4,6,8]
 print(g) # 结果:<generator object at 0x000002173F0EBC50>
 next(g) # 0
 next(g) # 2
 next(g) # 4
 next(g) # 6
 next(g) # 8
 next(g) # Traceback (most recent call last): ....StopIteration
 ?
 for x in g:
 print(x, end=' ') # 结果:0 2 4 6 8 

本文原创并首发于微信公众号【Python猫】,后台回复“爱学习”,免费获得20本精选电子书。

分类
python

Python的列表,()元组,字典详解(python列表元组基本操作)

前言

本章主要详细讲解?Python的?列表?,字典?,元组?


提示:以下是本篇文章正文内容,下面案例可供参考

元组:


python ()表示元组,元组是一种不可变的序列

1)创建如:tuple = (1,2,3) 取数据 tuple[0]…… tuple[0,2]…..tuple[1,2]……

2)修改元祖:元组是不可修改的

3)删除元组 del tuple

4)内置函数:

cmp(tuple1,tuple2):比较两个元组

len(tuple):计算元组的长度

max(tuple):最大值

min(tuple):最小值

tuple(seq):将列表转为元组

列表:

python []表示列表,列表是可变的序列

1)创建列表l = [1,2,3,4]取数据l[0]……..

2)列表可修改

3)内置函数

cmp(list1,list2):比较两个元祖

len(list):计算元祖的长度

max(list):最大值

min(list):最小值

list(seq):将元祖转为列表

list.append(obj):在列表末尾新增对象

list.pop():移除某个数据

list.remove:移除某个列表中匹配的第一个值

list.sort():排序

list.reverse():反转列表

list.count(bj):计算对象在列表中出现的次数

list.insert(index,obj) :在某个位置插入对象

字典?:

python {} 字典;字典是可变的容器,使用比较灵活

1)创建字典:dict = {"a":1,"b":2}. 字典是一对:key, value的键值对 取数据dict['a'],

2)可修改

3)删除:del dict["a"] 删除某对数据 del dict 删除字典 dict.clear()清除字典所有条目

4)内置函数

cmp(dict1,dict2):比较两个元祖

len(dict):计算元祖的长度

dict.clear():删除字典数据

dict.get(key, default=None):返回指定值,如果没有返回指定默认值

dict.has_key(key):判断值是否存在,返回true,false

dict.item()以列表值返回返回可遍历的(键,值)的元祖

dict.key()返回字典所有的key值

分类
python

python 实现list 全排列(pythonlist 排序方法)

在一些场合中,我们需要对list中的元素进行全排列或组合。虽然现成的函数可以调用,但是如果让我们自己去实现,该如何实现呢 ?

用递归的方法实现如下:

1、循环遍历list,将当前的元素放在一个新的list中。

2、将剩余的元素作为list,递归调用。

代码如下:

def fullindex(arglist):

length = len(arglist)

if length == 1 : return arglist[0]

res = []

for ch in arglist:

if ch == ‘0’ : continue

indexlist = []

temp = []

temp.extend(arglist)

temp.remove(ch)

indexlist.extend(fullindex(temp))

for item in indexlist:

arr = []

arr.extend(ch)

arr.extend(item)

res.append(arr)

# print “in full :”,res

return res

在这个当中,需要注意的是:

1、list的赋值,是按照引用传递的,因此,将剩余的元素赋值给一个临时的list,必须要通过extend的方式。

2、extend 和append的区别在于,append是将一个对象添加到指定的list后面,而extend 是将对象中的元素,添加到list中。这个地方的对象有可能是list , 也有可能是单个的元素,也有可能是一个tuple等等。 如a = [1,2,3] b = [3,4] a.append(b) 的结果: [1,2,3,[3,4]] , 而a.extend(b) 的结果是:[1,2,3,3,4]

python 自带函数实现全排列的方式:

import itertools

print list(itertools.permutations([1, 2, 3,4],3))

分类
python

Python笔记:List相关操作(python中list的常用方法)

List相关操作小例子

获取list的下标和值

>>> mylist = ['a', 'b', 'c', 'd']
>>> for index, value in enumerate(mylist):
...         print(index, value)
...
0 a
1 b
2 c
3 d
>>>

删除list中的空字符

list1 = ['1', '','2', '3', '  ', ' 4  ', '  5', '    ','6 ', '', '     ',None, '7']
print(list1)
list2 = list(filter(None, list1)) 
print(list2) # ['1', '2', '3', '  ', ' 4  ', '  5', '    ', '6 ', '     ', '7']
list3 = [x.strip() for x in list2]
print(list3) # ['1', '2', '3', '', '4', '5', '', '6', '', '7']
list4 = list(filter(None, list3))  
print(list4) # ['1', '2', '3', '4', '5', '6', '7']

删除list元素

使用remove、pop和del方法参删除list中的某个元素

>>> mylist = ['a', 'b', 'c', 'd','e','f','g','h']
>>> mylist.remove('a')
>>> mylist
['b', 'c', 'd', 'e', 'f', 'g', 'h']
>>> mylist.pop(0)
'b'
>>> mylist
['c', 'd', 'e', 'f', 'g', 'h']
>>> del mylist[0]
>>> mylist
['d', 'e', 'f', 'g', 'h']
>>> del mylist[0:2]
>>> mylist
['f', 'g', 'h']
>>> del mylist
>>> mylist
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'mylist' is not defined
>>>

计算中位数

def get_median(data):
     data.sort()
     half = len(data) // 2
     return (data[half] + data[~half]) / 2

将字符串list转换为int

>>> test_list = ['1', '4', '3', '6', '7']
>>> test_list = list(map(int, test_list))
>>> test_list
[1, 4, 3, 6, 7]
>>>

合并、连接字符串list

>>> test_list = ['192', '168', '0', '1']
>>> test_list = '.'.join(test_list)
>>> test_list
'192.168.0.1'
>>>

取多个字符串/list交集

>>> a = ['123','234','1253']
>>> list(reduce(lambda x,y : set(x) & set(y), a))
['2', '3']
>>> b = [[1,2,3],[1,2],[1,2,3,4],[12,1,2]]
>>> list(reduce(lambda x,y : set(x) & set(y), b))
[1, 2]

合并字典value值

>>> mydict = {0:"hello ", 1:"world"}
>>> mylist =reduce(lambda x, y : x + y, mydict.values())
>>> mylist
'hello world'
>>> mydict = {0:[1,2,3,4], 1:[2,3,4,5,6]}
>>> mylist = list(reduce(lambda x, y : x + y, mydict.values()))
>>> mylist
[1, 2, 3, 4, 2, 3, 4, 5, 6]
>>> field_counters = dict(Counter(mylist))
>>> field_counters
{1: 1, 2: 2, 3: 2, 4: 2, 5: 1, 6: 1}

列表排列组合

列表排列组合可以使用迭代器模块itertools

列表排列

import itertools

l = [1, 2, 3]
print(list(itertools.permutations(l, 2)))
print(list(itertools.permutations(l, 3)))

结果:

[(1, 2), (1, 3), (2, 1), (2, 3), (3, 1), (3, 2)]
[(1, 2, 3), (1, 3, 2), (2, 1, 3), (2, 3, 1), (3, 1, 2), (3, 2, 1)]

组合

import itertools

l = [1, 2, 3]
print(list(itertools.combinations(l, 2)))

结果:

[(1, 2), (1, 3), (2, 3)]

多个列表元素组合:笛卡尔积

两个列表元素两两组合:

import itertools

l = [1, 2, 3]
l1 = [11, 12, 13]
l2 = [21, 22, 23]
# 笛卡尔积
print(list(itertools.product(l, l)))
print(list(itertools.product(l, repeat=2)))
print(list(itertools.product(l1, l2)))

结果:

[(1, 1), (1, 2), (1, 3), (2, 1), (2, 2), (2, 3), (3, 1), (3, 2), (3, 3)]
[(1, 1), (1, 2), (1, 3), (2, 1), (2, 2), (2, 3), (3, 1), (3, 2), (3, 3)]
[(11, 21), (11, 22), (11, 23), (12, 21), (12, 22), (12, 23), (13, 21), (13, 22), (13, 23)]