内容是对《Python爬虫开发:从入门到实战》的摘录、理解、代码实践和遇到的问题。
在爬虫的开发中,需要把有用的信息从一大串文本中提取出来。正则表达式是提取信息的方法之一,虽然不是最简单也不是最高效的方法,但是最直接的,而且在某些情况下只有正则表达式才能达到目的。
该部分主要知识:
- 正则表达式的基本符号
- 如何在python中使用正则表达式
- 正则表达式的提取技巧
- Python读写文本文件和CSV文件
正则表达式Regular Expression
正则表达式是一段字符串,可以表示一段有规律的信息。Python自带一个正则表达式的模块,通过这个模块可以查找、提取、替换一段有规律的信息。
使用正则表达式的步骤:
- 寻找规律
- 使用正则符号表示规律
- 提取信息
正则表达式基本符号
点号“.”
一个点号可以代替除了换行符以外的任何一个字符。
星号“*”
一个星号可以表示它前面的一个子表达式(普通字符、另一个或几个正则表达式符号)0次到无限次。
问号“?”
问号表示它前面的子表达式出现0次或1次。注意是英文问号。
反斜杠“\”
转义符,和其他字符配合使用,把特殊符号变成普通符号或把普通符号变成特殊符号。
数字“\d”
正则表达式里面使用“\d”来表示一位数字。
小括号“()”
小括号可以把括号里面的内容提取出来。
在Python中使用正则表达式
Python的正则表达式模块名字为“re”(也即regular expression的首字母缩写),需要先导入再使用,导入语句为:
import re
findall
以列表的形式返回所有满足要求的字符串
findall的函数原型: re.findall(pattern, string, flags=0)
其中pattern表示正则表达式,string表示需要提取的字符串,flags表示一些特殊功能的标志(flags参数可以省略,当不省略时具有一些辅助功能,如忽略大小写、忽略换行符等)。
findall的结果是一个列表,包含了所有匹配到的结果,如果没有匹配到结果就会返回一个空列表。
import re
content = '密码1是:123, 密码2是:345, 密码3是:564,密码4未定'
password_list = re.findall(':(.*?),', content)
cantFindAnythingSample = re.findall('名字是:(.*?),', content)
print(password_list)
print(cantFindAnythingSample)
['123', '345', '564']
[]
在一个正则表达式中使用多个()
正则表达式是用来表示出需要的数据的格式,例如上面例子,我们所需要提取的密码串总是以”:”开头,以”,”结尾,所以正则表达式是‘:(.*?),‘,但提取结果中我们并不需要:和, 所以并没有把它们放入括号内。即:我们可以使用小括号()来选取需要返回的部分。
如果一个正则表达式中包含多个小括号,findall()返回的仍是一个列表,但列表内的元素变为元组,正则表达式里有几个小括号,元组里就有几个元素,以下面同时匹配账号和密码为例:
import re
content = '网站1的账号是:123,密码是:234,网站2的账号是:kae,密码是:345a,网站3的账号是:ryrt,密码是:234,'
account_password = re.findall('账号是:(.*?),密码是:(.*?),', content)
print(account_password)
[('123', '234'), ('kae', '345a'), ('ryrt', '234')]
注意中英文标点符号
注意:使用正则表达式的过程中,中英文标点符号混淆常常会导致各种问题,特别是冒号、逗号和引号,在中英文输入法中看上去非常相似但对于计算机而言是不一样的,需要格外注意。
re.findfall() flags参数使用示例,以忽略换行符为例
可以在flags参数 的位置填入re.S以此实现忽略换行符
import re
content = '''
密码是:123
456,
'''
withoutFlags = re.findall('密码是:(.*?),', content)
withFlags = re.findall('密码是:(.*?),', content, re.S)
print(withoutFlags)
print(withFlags)
[]
['123\n456']
可见当存在待提取数据中存在换行符时,可能导致整个数据都提取不到,使用re.S可以防止这种情况,虽然提取的结果中多出了‘\n‘但可以在后期数据清洗时洗掉。
search
search()的用法和findall()一样,不同的是search()只会返回第一个满足要求的字符串,一旦找到符合要求的内容就会停止查找。在文本很大且只需要第一个数据时能大幅提高效率。
search()的函数原型是 re.search(pattern, string, flags=0)
如果没有匹配到任何数据,则返回None,如果匹配成功,则返回一个正则表达式对象,要通过.group()方法来获取里面的值,只有.group()里的参数用于指定返回正则表达式中第几个括号里的值(从1开始)。
import re
content = '网站1的账号是:dasd,密码是:123,网站2的账号是:fdgs,密码是:234,网站3...'
cantGetAnythingSample = re.search('用户名是:(.*?),', content)
getFirst = re.search('账号是:(.*?),密码是:(.*?),', content)
invalid1 = getFirst # search()得到的结果需要使用.group()方法获取括号里的值
invalid2 = getFirst.group(0) # .group()里的参数从1开始,0是无效的
valid1 = getFirst.group(1) # 有效写法,返回第正则表达式第一个括号匹配到的东西,即账号
valid2 = getFirst.group(2) # 有效写法,返回第正则表达式第二个括号匹配到的东西,即密码
print(cantGetAnythingSample)
print(invalid1)
print(invalid2)
print(valid1)
print(valid2)
None
<re.Match object; span=(4, 21), match='账号是:dasd,密码是:123,'>
账号是:dasd,密码是:123,
dasd
123
“.*”和“.*?”的区别
“.*”:贪婪模式,获取最长的满足条件的字符串
“.*?”:非贪婪模式,获取最短的满足条件的字符串
import re
content = '网站1的账号是:123,密码是:234,网站2的账号是:kae,密码是:345a,网站3的账号是:ryrt,密码是:234,'
withQueMark = re.findall('账号是:(.*?),', content)
withoutQueMark = re.findall('账号是:(.*),', content)
print(withQueMark)
print(withoutQueMark)
['123', 'kae', 'ryrt']
['123,密码是:234,网站2的账号是:kae,密码是:345a,网站3的账号是:ryrt,密码是:234']
Python文件操作
Python的文件操作涉及对文件的读/写与编码的处理,是学习爬虫的必备知识。
使用Python读写文本文件
open
open用于打开一个文件并创建一个文件对象。有两种写法。
1、需要手动关闭文件
f = open('文件路径', '文件操作方式', encoding='utf-8')
对文件进行操作
f.close()
2、使用Python的上下文管理器,不需要手动关闭文件,只要代码退出了缩进,Python就会自动关闭文件。
with open('文件路径', '文件操作方式', encoding='utf-8') as f:
对文件进行操作
因为程序开发中经常会出现忘记关闭文件的情况,所以推荐使用第二种写法。
open()参数
‘文件路径‘ 可以是绝对路径,也可以是相对路径。其中相对路径是相对于现在的工作区而言的,并不总是相对于正在运行的这个Python文件的路径(?)。在本章中先直接将文本文件和Python文件放在一起,这样可以直接使用文件名打开文本文件。
‘文件操作方式‘ 在读文件的时候,这个参数可以省略,也可以写成‘r’。在写文件的时候写成’w’或‘a’,使用’w’新内容会覆盖原文件,而用’a’则会把新内容写到原文件的末尾。
‘encoding’ 可以指定文件的编码格式,从而避免出现乱码。(如果文件是在Windows中创建的,并且使用UTF-8打开文件出现了乱码,可以把编码格式改为GBK)
读文本文件
文件内容:
with open('text.txt', encoding='utf-8') as f:
res1 = f.readlines() # 读取所有行并以列表的形式返回
print(res1)
['第一行\n', '第二行\n', '第三行']
写文本文件
with open('text.txt', 'w', encoding='utf-8') as f:
f.write("直接将一整个字符串写入文本文件中用write()方法")
with open('text.txt', 'w', encoding='utf-8') as f:
# 把一个列表里所有的内容写入文本文件中用writelines()方法
f.writelines(['第一段话', '第二段话', '第三段话'])
需要注意不管是哪种方法都是不会自动换行的,如果需要换行需要人工输入换行符,方法如下:
f.write('\n')
使用Python读写CSV文件
CSV文件本质上就是文本文件,但如果直接用文本编辑器打开可读性并不高。用Excel或Numbers打开,则能得到可读性很高的表格。
Python自带操作CSV的模块csv,使用这个模块可以将CSV文件的内容转换为Python的字典。
读CSV文件
由于CSV文件本质上是一个文本文件,所以需要先以文本文件的方式打开,再将文件对象传递给csv模块
CSV文件内容:
import csv
with open('file.csv', encoding='utf-8') as f:
reader = csv.DictReader(f)
for row in reader:
print(row)
OrderedDict([('\ufeffuserName', 'uer1'), ('age', '14')])
OrderedDict([('\ufeffuserName', 'uer2'), ('age', '21')])
【Python问题解决】利用Python读取文件时出现\ufeff的原因及解决办法_python ufeff_奋斗中的编程菜鸟的博客-CSDN博客
原因分析
utf-8编码的文件时开头会有一个多余的字符\ufeff,在读文件时会读到\ufeff
解决办法
只需改一下编码就行,把 UTF-8 编码 改成 UTF-8-sig编码即可
其中得到的row是OrderedDict(有序字典),可以直接像普通字典那样使用
with open('file.csv', encoding='UTF-8-sig') as f:
reader = csv.DictReader(f)
for row in reader:
userName = row['userName']
age = row['age']
print('用户名:{}, 年龄:{}'.format(userName, age))
用户名:uer1, 年龄:14
用户名:uer2, 年龄:21
写CSV文件
Python可以把一个字典或者一个包含字典的列表写成CSV文件。
写CSV文件时需要指定列名,列名要和字典的Key一一对应。
csv.DictWriter(文件对象f,fieldnames = 字典的Key列表)
import csv
data = [
{'name': 'uer1', 'age': 24, 'sex': 'male'},
{'name': 'uer9', 'age': 22, 'sex': 'male'},
{'name': 'uer12', 'age': 20, 'sex': 'female'}
]
with open('file.csv', 'w', encoding='UTF-8-sig') as f:
writer = csv.DictWriter(f, fieldnames=['name', 'age', 'sex']) # 只到一行为止文件里什么都没有
writer.writeheader() # 写入列行名
writer.writerows(data) # 写入数据
writer.writerow({'name': 'uer14', 'age': 26, 'sex': 'female'})
最后一行演示了可以用.writerow(字典)写入单个字典,不需要关心Key的顺序,但字典里不能存在fieldnames里面没有的Key,也不能缺少fieldnames里已有的Key。
Python 写入 csv 文件中空行解决办法_李正旺的Python空间的博客-CSDN博客
改为
with open('file.csv', 'w', encoding='UTF-8-sig', newline="") as f:
后,空行问题解决。