【Python爬虫】Python爬虫开发(从入门到实战(谢乾坤)[1-3])

【Python爬虫】Python爬虫开发(从入门到实战(谢乾坤)[1-3])
文章图片

第2章 Python 基础 2.3 Python的数据结构和控制结构 元组和列表的区别:

列表生成以后还可以往里面继续添加数据,也可以从里面删除数据;但是元组一旦生成就不能修改。如果它里面只有整数、浮点数、字符串、另一个元组,就既不能添加数据,也不能删除数据,还不能修改里面数据的值。但是如果元组里面包含了一个列表,那么这个元组里面的列表依旧可以变化。
切片操作
【【Python爬虫】Python爬虫开发(从入门到实战(谢乾坤)[1-3])】格式为:
变量名[开始位置下标:结束位置下标:步长]

切片的结果包括“开始位置下标”所对应的元素,但是不包括“结束位置下标”所对应的元素。
列表末尾添加元素
Append,例如:
>>> list_4 = ['Python', ’爬虫’]? >>> print(list_4)? ['Python', ’爬虫’]? >>> list_4.append(’一’)? >>> print(list_4)? ['Python', ’爬虫’, ’一’]? >>> list_4.append(’酷’)? >>> print(list_4)? ['Python', ’爬虫’, ’一’, ’酷’]

元组和字符串不能添加新的内容,不能修改元组里面的非可变容器元素,也不能修改字符串里面的某一个字符。
通过Key来从字典中读取对应的Value,
有3种主要的格式:
变量名[key]? 变量名.get(key)? 变量名.get(key, ’在找不到key的情况下使用这个值’)

例如:
>>> example_dict = {'superman': ’超人是一个可以在天上飞的两足兽’, ’天才’: ’天才跑在时代的前面,把时代拖得气喘吁吁。', ? 'xx': 0, 42: '42是一切的答案’}? >>> print(example_dict[’天才’])? 天才跑在时代的前面,把时代拖得气喘吁吁。? >>> print(example_dict.get(42))? 42是一切的答案? >>> print(example_dict.get(’不存在的key'))? None? >>> print(example_dict.get(’不存在的key', ’找不到’))? 找不到

使用方括号的方式来读取字典的Value时,一定要保证字典里面有这个Key和它对应的Value,否则程序会报错。
使用get来读取,如果get只有一个参数,那么在找不到Key的情况下会得到“None”;如果get有两个参数,那么在找不到Key的情况下,会返回第2个参数。
修改已存在字典的Key对应Value/增加新的Key-Value对
变量名[key] = ’新的值’

如果Key不存在,就会创建新的Key-Value对;如果Key已经存在,就会修改它的原来的Value。例如:
>>> existed_dict = {'a': 123, 'b': 456}? >>> print(existed_dict)? {'b': 456, 'a': 123}? >>> existed_dict['b'] = ’我修改了b'? >>> print(existed_dict)? {'b': ’我修改了b', 'a': 123}? >>> existed_dict['new'] = ’我来也’? >>> print(existed_dict)? {'b': ’我修改了b', 'a': 123, 'new': ’我来也’}

需要特别注意的是,字典的Key的顺序是乱的,所以不能认为先添加到字典里面的数据就排在前面。
集合去重
集合最大的应用之一就是去重。
例如,把一个带有重复元素的列表先转换为集合,再转换回列表,那么重复元素就只会保留一个。 把列表转换为集合需要使用set()函数,把集合转换为列表使用list()函数:
duplicated_list = [3, 1, 3, 2, 4, 6, 6, 7, 's', 's', 'a'] unique_list = list(set(duplicated_list)) print(unique_list)

[1, 2, 3, 4, 's', 6, 7, 'a']

由于集合与字典一样,里面的值没有顺序,因此使用集合来去重是有代价的,代价就是原来列表的顺序也会被改变。
使用字典实现多重条件控制
如果有多个if,写起来会很烦琐,例如下面这一段代码:
if state == 'start':? code = 1? elif state == 'running':? code = 2? elif state == 'offline':? code = 3? elif state == 'unknown':? code = 4? else:? code = 5

使用“if…elif…else…”会让代码显得冗长。如果使用字典改写,代码就会变得非常简洁:
state_dict = {'start': 1,'running': 2, 'offline': 3, 'unknown': 4}? code = state_dict.get(state, 5)

for循环
for 循环从列表中取出每个元素
name_list=['a','b','c','d'] for name in name_list: print(name)

for循环从每个字符串里面获得一个字符
title="大家好,我叫EasyLake" for character in title: print (character)

这里的每一个汉字、每一个字母、每一个标点符号都会被for循环分开读取。循环第1次得到的是“大”,第2次得到的是“家”,第3次得到的是“好”,以此类推。
在做爬虫的时候会遇到需要把列表展开的情况,常犯的一个错误就是把字符串错当成了列表展开。这个时候就会得到不正常的结果。
for循环把字典展开得到里面每一个key
menu_dict = {'红烧肉':'100元','水煮肉片':'50元','鸡汤':'1角'} for key in menu_dict: print('菜品:{}'.format(key)) print('价格:{}'.format(menu_dict[key])) print("---------------")

通过指定range里面的数字,可以控制循环的执行次数。需要特别注意的是,i是从0开始的。
在某些特殊的情况下,确实需要循环永远运行,这个时候需要这样写:
import time? while True:? 你要执行的代码? time.sleep(1)

如果要让循环永久运行,那么增加一个延迟时间是非常有必要的。time.sleep()的参数为一个数字,单位为秒。如果不增加这个延迟时间,就会导致循环超高速运行。在爬虫的开发过程中,如果超高速运行,很有可能导致爬虫被网站封锁。
第3章正则化表达式与文件操作 3.1 .1正则表达式的基本符号 1. 点号“.”:一个点号可以代替除了换行符以外的任何一个字符
一个点号可以代替除了换行符以外的任何一个字符,包括但不限于英文字母、数字、汉字、英文标点符号和中文标点符号。例如,有如下几个不同的字符串:
kingname? kinabcme? kin123me? kin我是谁me? kin嗨你好me? kin"m"me`

这些字符串的前3个字符都是“kin”,后两个字符都是“me”,只有中间的3个字符不同。如果使用点号来表示,那么全部都可以变成kin…me的形式,中间有多少个字就用多少个点。
2.星号“*”
只有一个星号 *:它前面的子表达式0次到无限次 一个星号表示它前面的一个子表达式(普通字符、另一个或几个正则表达式符号)0次到无限次。
例如,有如下几个不同的字符串:
如果快乐你就笑哈? 如果快乐你就笑哈哈? 如果快乐你就笑哈哈哈哈? 如果快乐你就笑哈哈哈哈哈哈哈哈哈

这些字符串里面,“哈”字重复出现,所以如果用星号来表示,那么就可以全部变成:
如果快乐你就笑哈* 如果快乐你就笑*

由于星号可以表示它前面的字符0次,所以即使写成“如果快乐你就笑”,没有“哈”字,也是满足这个正则表达式的。
一个点号+一个星号 .* 既然星号可以表示它前面的字符,那么如果它前面的字符是一个点号呢?例如下面这个正则表达式:
如.*哈

它表示在“如”和“哈”中间出现“任意多个除了换行符以外的任意字符”。这句话看起来有点绕,用下面几个字符串来说明,它们全部都可以用上面的这个正则表达式来表示:
如哈? 如果快乐哈? 如果快乐你就笑哈? 如果你知道1+1=2那么请计算地球的半径哈? 如aklsdjfjaf哈

3.问号“? ”
问号表示它前面的子表达式0次或者1次。注意,这里的问号是英文问号。
例如下面这两个不同的字符串:
?
笑起来。? 笑起来哈。

在汉字“来”和中文句号之间有0个或者1个“哈”字,都可以使用下面这个正则表达式来表示:
?
笑起来哈?。

点号+星号+问号 .*? 问号最大的用处是与点号和星号配合起来使用,构成“.*? ”。通过正则表达式来提取信息的时候,用到最多的也是这个组合。
下面的所有字符串:
如哈? 如果快乐哈? 如果快乐你就笑哈? 如果你知道1+1=2那么请计算地球的半径哈? 如aklsdjfjaf哈

都可以用下面这个正则表达式来表示:
?如.*?哈`
.* 和.* ?d 的区别:
前者为贪婪模式,获取最长的满足条件的字符串;后者为非贪婪模式,获取最短的能满足条件的字符串。
前者最多只能匹配一个,后者可以匹配多个。
4.反斜杠/
反斜杠不仅可以把特殊符号变成普通符号,还可以把普通符号变成特殊符号。例如“n”只是一个普通的字母,但是“\n”代表换行符。
【Python爬虫】Python爬虫开发(从入门到实战(谢乾坤)[1-3])
文章图片

5.数字“\d”:表示一位数字
正则表达式里面使用“\d”来表示一位数字。为什么要用字母d呢?因为d是英文“digital(数字)”的首字母。
再次强调一下,“\d”虽然是由反斜杠和字母d构成的,但是要把“\d”看成一个正则表达式符号整体。
如果要提取两个数字,可以使用\d\d;如果要提取3个数字,可以使用\d\d\d。但是如果不知道这个数有多少位怎么办呢?就需要用*号来表示一个任意位数的数字。
下面一段字符串:
?
是123455677,请记住它。? 是1,请记住它。? 是66666,请记住它。

全部都可以使用下面这个正则表达式来表示:
?
\d*:一个任意位数的数字。 6.小括号“()”:把括号里面的内容提取出来。
前面讲到的符号仅仅能让正则表达式“表示”一串字符串。但是如果要从一段字符串中“提取”出一部分的内容应该怎么办呢?这个时候就需要使用小括号了。
有如下一个字符串:
?我的密码是:12345abcde你帮我记住。
可以看出,这里的密码左边有一个英文冒号,右边有一个汉字“你”。当构造一个正则表达式:.*?你时,得到的结果将会是:
?:12345abcde你
然而,冒号和汉字“你”并不是密码的一部分,如果只想要“12345abcde”,就需要使用括号:
:(.*? )你
得到的结果就是:
12345abcde
3.1.2在Python中使用正则表达式 Python的正则表达式模块名字为“re”,也就是“regular expression”的首字母缩写。在Python中需要首先导入这个模块再进行使用。导入的语句为:
?import re
1.findall:以列表的形式返回所有满足要求的字符串
Python的正则表达式模块包含一个findall方法,它能够以列表的形式返回所有满足要求的字符串。
findall的函数原型为:
?re.findall(pattern, string, flags=0)
pattern表示正则表达式,string表示原来的字符串,flags表示一些特殊功能的标志。
findall的结果是一个列表,包含了所有的匹配到的结果。如果没有匹配到结果,就会返回空列表。
当需要提取某些内容的时候,使用小括号将这些内容括起来,这样才不会得到不相干的信息。如果包含多个“(.? )”怎么返回呢?如图3-2所示,返回的仍然是一个列表,但是列表里面的元素变为了元组,元组里面的第1个元素是账号,第2个元素为密码。
?
图3-2 多个括号内的内容会以元组形式返回
请注意代码中的冒号和逗号,图3-1代码中为中文冒号和中文逗号;图3-2代码中为英文冒号和英文逗号。在实际使用正则表达式的过程中,中英文标点符号混淆常常会导致各种问题。特别是冒号、逗号和引号,虽然中英文看起来非常相似,但实际上中文冒号和英文冒号是不一样的,中文逗号和英文逗号也是不一样的。在某些字体里面,这种差异甚至无法察觉,因此在涉及正则表达式中的标点符号时,最好直接复制粘贴,而不要手动输入。
函数原型中有一个flags参数。这个参数是可以省略的。当不省略的时候,具有一些辅助功能,例如忽略大小写、忽略换行符等。这里以忽略换行符为例来进行说明,如图3-3所示。
?
图3-3 使用re.S作为flag来忽略换行符
在爬虫的开发过程中非常容易出现这样的情况,要匹配的内容存在换行符“\n”。要忽略换行符,就需要使用到“re.S”这个flag。虽然说匹配到的结果中出现了“\n”这个符号,不过总比什么都得不到强。内容里面的换行符在后期清洗数据的时候把它替换掉即可。
?
V3-6 search的使用
2.search
search()的用法和findall()的用法一样,但是search()只会返回第1个满足要求的字符串。一旦找到符合要求的内容,它就会停止查找。对于从超级大的文本里面只找第1个数据特别有用,可以大大提高程序的运行效率。
search()的函数原型为:
?
re.search(pattern, string, flags=0)
对于结果,如果匹配成功,则是一个正则表达式的对象;如果没有匹配到任何数据,就是None。如果需要得到匹配到的结果,则需要通过.group()这个方法来获取里面的值,如图3-4所示。
?
图3-4 使用.group()来获取search()方法找到的结果
只有在.group()里面的参数为1的时候,才会把正则表达式里面的括号中的结果打印出来。
group()的参数最大不能超过正则表达式里面括号的个数。参数为1表示读取第1个括号中的内容,参数为2表示读取第2个括号中的内容,以此类推,如图3-5所示。
?
图3-5 .group()的参数意义
?
V3-7 (.
)和(.? )的区别
3.“.
”和“.? ”的区别
在爬虫开发中,.
?这3个符号大多数情况下一起使用。
点号表示任意非换行符的字符,星号表示匹配它前面的字符0次或者任意多次。所以“.”表示匹配一串任意长度的字符串任意次。这个时候必须在“.”的前后加其他的符号来限定范围,否则得到的结果就是原来的整个字符串。
如果在“.”的后面加一个问号,变成“.? ”,那么可以得到什么样的结果呢?问号表示匹配它前面的符号0次或者1次。于是.?的意思就是匹配一个能满足要求的最短字符串。
这样说起来还是非常抽象,下面通过一个实际的例子来进行说明。请看下面这一段话:
?
我的微博密码是:1234567, QQ密码是:33445566, 银行卡密码是:888888, Github密码是:999abc999,帮我记住它们
这段话有一个显著的规律,即密码是:xxxxxx, ”,也就是在“密码是”这3个汉字的后面跟一个中文的冒号,冒号后面是密码,密码后面是中文的逗号。
如果想把这4个密码提取出来,可以构造以下两个正则表达式:
?
密码是:(.
),?
密码是:(.? ),
配合Python的findall方法,得到结果如图3-6图所示。
?
图3-6 使用“.*”和“.*? ”返回的结果
使用“(.
)”得到的是只有一个元素的列表,里面是一个很长的字符串。
使用第2个正则表达式“(.? )”,得到的结果是包含4个元素的列表,每个元素直接对应原来文本中的每个密码。
举一个例子,10个人肩并肩并排站着,使用“(.
)”取到了第1个人左手到第10个人右手之间的所有东西,而使用“(.? )”取到的是“每个人”的左手和右手之间的东西。
一句话总结如下。
①“.
”:贪婪模式,获取最长的满足条件的字符串。
②“.? ”:非贪婪模式,获取最短的能满足条件的字符串。
3.1.3 正则表达式提取技巧
1.不需要compile
网上很多人的文章中,正则表达式使用re.compile()这个方法,导致代码变成下面这样:
?
import re?
example_text = ’我是kingname, 我的微博账号是:kingname, 密码是:12345678, QQ账号是:99999, 密?
码是:890abcd, 银行卡账号是:000001, 密码是:654321, Github账号是:99999@qq.com, 密码?
是:7777love8888, 请记住他们。'?
new_pattern=re.compile(’账号是:(.
? ), 密码是:(.*? ), ', re.S)?
user_pass = re.findall(new_pattern, example_text)?
print(user_pass)
?
V3-8 正则表达式提取技巧
这种写法虽然结果正确,但纯粹是画蛇添足,是对Python的正则表达式模块没有理解透彻的体现,是从其他啰嗦的编程语言中带来的坏习惯。如果阅读Python的正则表达式模块的源代码,就可以看出re.compile()是完全没有必要的。
对比re.compile()和re.findall()在源代码中的写法,如图3-7所示的两个方框。

    推荐阅读