一 正则表达式
- 常见的通配符
. 当前目录
… 当前目录的上一级目录
[0-9]表示数字0-9
[a-z]表示字母a-z
[A-Z]表示大写字母A-Z - 通配符的使用
找出 /etc 下以 “.conf"结尾的文件
以前可能会用到 endswith(”.conf")这样来进行查找
现在,也可用 glob模块来查找
import glob
print(glob.glob('/etc/*.conf'))
但是,如果再加上含有大写字母的,以 ".conf"结尾的文件呢?
这样endswith就解决不了了
print(glob.glob('/etc/*[A-Z]*.conf'))
当然这只是简单的查找,一旦问题复杂话,glob就不好处理了
- 正则表达式的引出
这里就需要导入 re 模块
import re
假如,一个简单的字符串如下:
s='home/kiosk/anaconda2/envs/mysql/bin/python/home/kiosk/PycharmProjects/py'
那么,正则表达式是如何来书写的呢?
一个完整的正则表达式包括(第一个参数是你正则的规则, 第二个参数是检测的字符串)
re 模块里面有三种的查找方式(findall、match、search)
1). findall
print(re.findall(r'kiosk',s))
findall 会将查找到符合要求,放到一个列表里面
2). match
print(re.match(r'home',s))
match有点不一样,如果找到匹配, 则返回一个对象
这里就需要用到 group 方法了
print(re.match(r'home',s).group())
还有一点需要注意的是: match 方法是从左往右依次匹配的
a = re.match(r"westos", "hellowestoshello")
print(a)
如果没有找到匹配,则返回None
3). search
print(re.search(r'kiosk',s))
可以看到 search 返回的也是一个对象
obj = re.search(r'kiosk', s)
print(obj.group())
同样要用到 group方法
要注意的一点时,只会返回第一个值
- 正则表达式中的特殊字符类
.: 匹配除了\n之外的任意字符; [.\n]
\d: digit–(数字), 匹配一个数字字符, 等价于[0-9]
\D: 匹配一个非数字字符, 等价于[^0-9]
\s: space(广义的空格: 空格, \t, \n, \r), 匹配单个任何的空白字符;
\S: 匹配除了单个任何的空白字符;
\w: 字母数字或者下划线, [a-zA-Z0-9_]
\W: 除了字母数字或者下划线, [^a-zA-Z0-9_]
1). .
print(re.findall(r'.','正义永远\n不会迟到800s'))
2). d、D
print(re.findall(r'\d','正义永远不会迟到800s'))
print(re.findall(r'\D','正义永远不会迟到800s'))
3). w、W
print(re.findall(r'\w','123正义df永远_不会迟到&'))
print(re.findall(r'\W','123正义df永远_不会迟到&'))
4). s、S
print(re.findall(r'\s','\n正义\t永远\r不会 迟到'))
print(re.findall(r'\S','\n正义\t永远\r不会 迟到'))
- 指定字符出现指定次数
匹配字符出现次数:
: 代表前一个字符出现0次或者无限次; d, .*
+: 代表前一个字符出现一次或者无限次; d+
?: 代表前一个字符出现1次或者0次; 假设某些字符可省略, 也可以不省略的时候使用
第二种方式:
{m}: 前一个字符出现m次;
{m,}: 前一个字符至少出现m次; * == {0, }; + ==={1,}
{m,n}: 前一个字符出现m次到n次; ? === {0,1}
比如匹配电话号码:
pattern=r'\d{3}[\s-]?\d{4}[\s-]?\d{4}'
print(re.findall(pattern,'183 3345 3351'))
print(re.findall(pattern,'18333453351'))
- 表示边界
^:表示以什么开头
$:表示以什么结尾 - 表示分组
| : 匹配| 左右任意一个表达式即可;
(ab): 将括号中的字符作为一个分组
\num: 引用分组第num个匹配到的字符串
(?P): 分组起别名
(?P=name) : 引用分组的别名
1). |
import re
# westos 或者 hello
print(re.match(r"westos|hello", "helloaaa").group())
print(re.match(r"westos|hello", "westoshelloaaa").group())
执行的结果:
2). (ab)
import re
print(re.findall(r'(westos|hello)\d+', 'westos1hello2'))
pattern = r'<span class="red">(\d+)</span>'
s = '<span class="red">31</span>'
print(re.findall(pattern, s))
执行的结果:
当使用分组时,可以看出 findall 只能获取到分组里面的内容
import re
pattern = r'(<span class="red">(\d+)</span>)'
s = '<span class="red">31</span>'
print(re.findall(pattern, s))
print(re.findall(r'((westos|hello)\d+)', 'westos1hello2'))
执行的结果:
当findall不能满足时, 这是就需要考虑使用search 或者match
import re
Obj = re.search(r'(westos|hello)(\d+)', 'westos1hello2')
if Obj:
print(Obj.group())
print(Obj.groups())
else:
print('Not Found')
执行的结果:
3). \num
import re
s = '<html><title>正则表达式</title></html>'
pattern = r'<(\w+)><(\w+)>(\w+)</\w+></\w+>'
print(re.findall(pattern, s))
执行的结果:
没有用分组方法前可以这样写
但是发现有重复的代码块
使用分组方法后:
import re
s = '<html><title>正则表达式</tite></html>'
# 目前有三个分组, \1: 代指第一个分组的内容, \2: 代指第二个分组的内容,
pattern = r'<(\w+)><(\w+)>(\w+)</\2></\1>'
print(re.findall(pattern, s))
执行的结果:
4). (?P)
import re
s1 = 'http://www.westos.org/linux/book/'
pattern = 'http://[\w\.]+/(?P<courseName>\w+)/(?P<courseType>\w+)/'
print(re.findall(pattern, s1))
Obj = re.match(pattern, s1)
if Obj:
print(Obj.group())
print(Obj.groups())
print(Obj.groupdict())
else:
print('Not Found')
执行的结果:
从这里就可以看到 group、groups 和 groupdict 三者之间的区别
group 返回的是一个列表
groups 返回的是一个元组
groupdict 返回的是一个字典,通常与 (?P) 配合使用
- 匹配qq邮箱
找出列表中符和条件的邮箱地址, 并存储到/tmp/mail.txt文件中
邮箱地址以@qq.com结尾
@qq.com前面的内容由字母,数字或者下划线组成, 但至少4位, 最多20
位
import re
mailList =["[email protected]", "[email protected]", "[email protected]", "[email protected]"
"[email protected]", "[email protected]"]
# 判断邮件地址是否合法;
def ismailOK(mail_name):
reg = r"\w{4,20}@qq.com$"
reg = re.compile(reg)
a = re.match(reg, mail_name)
if a:
return True
else:
return False
mailOkList = [i for i in mailList if ismailOK(i)]
# 将符和条件的邮件地址写入文件 ;
with open("/tmp/mail.txt", "a+") as f:
for i in mailOkList:
f.write(i+"\n")
print(i)
执行的结果:
- re高级用法
search()方法: 只找到符和条件的第一个并返回;
findall()方法: 返回符合条件的所有内容;
sub()方法: 对符合正则的内容进行替换;
split()方法: 指定多个分隔符进行分割
1). sub()
将1000替换为0
import re
s = "阅读次数为1000, 转发次数为100"
reg = r"\d+"
a = re.search(reg, s)
a.group()
re.findall(reg, s)
print(re.sub(reg, '0' , s))
执行的结果:
在1000的基础上加1
import re
s = "阅读次数为1000, 转发次数为100"
reg = r"\d+"
a = re.search(reg, s)
def addNum(x):
a = int(x.group()) + 1
return str(a)
print(re.sub(reg, addNum, s))
执行的结果:
2). split()
用 :将字符串分割开
import re
s = "fentiao 18:18811112222"
print(re.split(r":| ", s))
执行的结果:
二 运用正则表达式来实现爬虫
- 获得贴吧上的邮箱
作为一个吧主,分享了一波资源,下面一群萌新留下了他的邮箱,你想给这些萌新将内容通过邮箱发送过去,总不能一个一个的来记录邮箱吧。
这里就用到了爬虫系统,将一个个邮箱爬去下来,记入一个文档中
实现步骤:
1).先根据正则表达式爬取到该帖子的总页数
2).再根据正则表达式爬取每一页的邮箱地址
3).最后将爬取到的邮箱写入文档中
from itertools import chain
from urllib.request import urlopen
import re
def getPageContent(url):
"""
获取网页源代码
:param url: 指定url内容
:return: 返回页面的内容(str格式)
"""
with urlopen(url) as html:
return html.read().decode('utf-8')
def parser_page(content):
"""
根据内容获取所有的贴吧总页数;
:param content: 网页内容
:return: 贴吧总页数
"""
pattern = r'<span class="red">(\d+)</span>'
data = re.findall(pattern, content)
return data[0]
def parser_all_page(pageCount):
"""
根据贴吧页数, 构造不同的url地址;并找出所有的邮箱
:param pageCount:
:return:
"""
emails = []
for page in range(int(pageCount)):
url = 'http://tieba.baidu.com/p/2314539885?pn=%d' %(page+1)
print("正在爬取:%s" %(url))
content = getPageContent(url)
# pattern = r'\w[-\w.+]*@[A-Za-z0-9][-A-Za-z0-9]+\.+[A-Za-z]{2,14}'
pattern = r'[a-zA-Z0-9][-\w.+]*@[A-Za-z0-9][-A-Za-z0-9]+\.+[A-Za-z]{2,14}'
findEmail = re.findall(pattern, content)
print(findEmail)
emails.append(findEmail)
return emails
def main():
url = 'http://tieba.baidu.com/p/2314539885'
content = getPageContent(url)
pageCount = parser_page(content)
emails = parser_all_page(pageCount)
print(emails)
with open('tiebaEmail.txt', 'w') as f:
for tieba in chain(*emails):
f.write(tieba + '\n')
main()
执行的结果:
可以看到爬取到了808个邮箱地址,节省了大量的时间
- 爬取指定贴吧页图片
原理是跟爬取邮箱地址是一样的,先爬取总页数,再将每一页的吧主发布的图片都爬取出来
import re
from urllib.request import urlopen
def getContent(url):
with urlopen(url)as f:
return f.read()
def parser_get_img_url(content):
pattern = r'<img class="BDE_Image".*?src="(http://.*?\.jpg)".*?>'
imgUrl = re.findall(pattern, content.decode('utf-8'))
return imgUrl
def main():
url = 'http://tieba.baidu.com/p/5255105555'
content = getContent(url)
imgLi = parser_get_img_url(content)
for index, img in enumerate(imgLi):
content = getContent(img)
with open('img/%s.jpg' % (index + 1), 'wb')as f:
f.write(content)
print('第%s个图片下载成功...' % (index + 1))
main()
执行的结果:
- 反爬虫第一步—伪装浏览器
如果还像平时那样爬取一些网站的话,一些网站是不允许这样做的,会拒绝你的访问
比如:
中国的一些银行信息
url = "http://www.cbrc.gov.cn/chinese/jrjg/index.html"
with urlopen(url) as html:
content = html.read().decode('utf-8')
print(content)
执行的结果:
403:拒绝访问
这时就要想办法了,它是不会拒绝浏览器访问的,依次我们可以伪装成浏览器进行爬取信息
url = "http://www.cbrc.gov.cn/chinese/jrjg/index.html"
# 如何伪装成浏览器访问?
# 1. 定义一个真实浏览器的代理名称
user_agent = "Mozilla/5.0 (X11; Linux x86_64; rv:45.0) Gecko/20100101 Firefox/45.0"
# 2. 写到请求页面的header里面去
req = request.Request(url,headers={'User-Agent': user_agent} )
# 3. 打开网页, 获取内容
print(urlopen(req).read().decode('utf-8'))
执行的结果:
这样我们就可以对一些禁止爬取的网站进行爬取了