本章工作任务
任务1:为什么学习Python爬虫
任务2:什么是爬虫
任务3:urllib的应用
任务4:cookie实际使用
任务5:正则表达式
本章技能目标及重难点
编号 | 技能点描述 | 级别 |
1 | 为什么学习爬虫 | ★ |
2 | 什么是爬虫 | ★★ |
3 | urllib的应用 | ★★★ |
4 | cookie实际使用 | ★★ |
5 | 正则表达式 | ★★ |
注: "★"理解级别 "★★"掌握级别 "★★★"应用级别
本章学习目标
本章开始学习Python爬虫,需要同学们理解为什么使用爬虫,它的概念、特点。最主要的是需要大家学会如何使用基础的爬虫库。
本章学习建议
本章适合有Python基础的学员学习。
本章内容(学习活动)
现在信息更新的非常快速,又迎来了大数据的时代, 各行各业如果不与时俱进,都将面临优胜劣汰,知识是不断的更新的,只有一技之长,才能立于不败之地。
网络爬虫,即 Web Spider,是一个很形象的名字。目前爬虫开发的语言的主要是 Python,本课程结合几个小的爬虫案例,帮助学员更好的学习爬虫开发。
那么为什么我们要学习Python爬虫呢?
1.1.1 Python语言的流行程度
现在全世界大约有几百万以上的Python语言的用户,大家可以看一下以下图片:
图1-1为2016 年 Spectrum评选出的排名前十的编程语言,Spectrum 的“交互式编程语言排行”让用户可以根据自己的喜好调整不同评价指标所占的权重,从而得到所需的排名。从该图可以看出Python在IEEE的会员用户语言使用中排名第三。
图1-2 TIOBE的数据就更能说明Python语言的地位,TIOBE 编程语言社区排行榜是编程语言流行趋势的一个指标,每月更新,这份排行榜排名基于互联网上有经验的程序员、 课程和第三方厂商的数量。排名使用著名的搜索引擎(诸如 Google、MSN、Yahoo!、Wikipedia、YouTube 以及 Baidu 等)进行计算。而Python语言排名第五。并趋于日益增长趋势。
1.1.2爬虫的强大作用
Ø 爬取数
Ø 分析并推送
Ø 资源批量下载
Ø 数据监控
Ø 社会计算方面的统计和预测
Ø 机器学习
Ø 网络爬虫
Ø 建立机器翻译的语料库
Ø 搭建大数据的数据库
1.1.3为什么Python适合写爬虫
1.抓取网页本身的接口
相比与其他静态编程语言,如java,c#,C++,python抓取网页文档的接口更简洁;相比其他动态脚本语言,如perl,shell,python的urllib2包提供了较为完整的访问网页文档的API。(当然ruby也是很好的选择)
此外,抓取网页有时候需要模拟浏览器的行为,很多网站对于生硬的爬虫抓取都是封杀的。这是我们需要模拟user agent的行为构造合适的请求,譬如模拟用户登陆、模拟session/cookie的存储和设置。在python里都有非常优秀的第三方包帮你搞定,如Requests,mechanize
2.网页抓取后的处理
抓取的网页通常需要处理,比如过滤html标签,提取文本等。python的beautifulsoap提供了简洁的文档处理功能,能用极短的代码完成大部分文档的处理。
其实以上功能很多语言和工具都能做,但是用python能够干得最快,最干净。Life is short, u need python.
接下来我们来介绍什么是爬虫。
1.2.1 爬虫的由来
随着网络的迅速发展,万维网成为大量信息的载体,如何有效地提取并利用这些信息成为一个巨大的挑战。搜索引擎(Search Engine),例如传统的通用搜索引擎AltaVista,Yahoo!和Google等,作为一个辅助人们检索信息的工具成为用户访问万维网的入口和指南。但是,这些通用性搜索引擎也存在着一定的局限性,如:
(1)不同领域、不同背景的用户往往具有不同的检索目的和需求,通用搜索引擎所返回的结果包含大量用户不关心的网页。
(2)通用搜索引擎的目标是尽可能大的网络覆盖率,有限的搜索引擎服务器资源与无限的网络数据资源之间的矛盾将进一步加深。
(3)万维网数据形式的丰富和网络技术的不断发展,图片、数据库、音频、视频多媒体等不同数据大量出现,通用搜索引擎往往对这些信息含量密集且具有一定结构的数据无能为力,不能很好地发现和获取。
(4)通用搜索引擎大多提供基于关键字的检索,难以支持根据语义信息提出的查询。
为了解决上述问题,定向抓取相关网页资源的聚焦爬虫应运而生。聚焦爬虫是一个自动下载网页的程序,它根据既定的抓取目标,有选择的访问万维网上的网页与相关的链接,获取所需要的信息。与通用爬虫(general purpose web crawler)不同,聚焦爬虫并不追求大的覆盖,而将目标定为抓取与某一特定主题内容相关的网页,为面向主题的用户查询准备数据资源。
网络爬虫是一个自动提取网页的程序,它为搜索引擎从万维网上下载网页,是搜索引擎的重要组成。传统爬虫从一个或若干初始网页的URL开始,获得初始网页上的URL,在抓取网页的过程中,不断从当前页面上抽取新的URL放入队列,直到满足系统的一定停止条件。聚焦爬虫的工作流程较为复杂,需要根据一定的网页分析算法过滤与主题无关的链接,保留有用的链接并将其放入等待抓取的URL队列。然后,它将根据一定的搜索策略从队列中选择下一步要抓取的网页URL,并重复上述过程,直到达到系统的某一条件时停止。另外,所有被爬虫抓取的网页将会被系统存贮,进行一定的分析、过滤,并建立索引,以便之后的查询和检索;对于聚焦爬虫来说,这一过程所得到的分析结果还可能对以后的抓取过程给出反馈和指导。聚集爬虫的逻辑结构图,如图1-3所示。
相对于通用网络爬虫,聚焦爬虫还需要解决三个主要问题:
(1) 对抓取目标的描述或定义;
(2) 对网页或数据的分析与过滤;
(3) 对URL的搜索策略。
1.2.2 爬虫的定义
网络爬虫,即Web Spider,是一个很形象的名字。把互联网比喻成一个蜘蛛网,那么Spider就是在网上爬来爬去的蜘蛛。网络蜘蛛是通过网页的链接地址来寻找网页的。
从网站某一个页面(通常是首页)开始,读取网页的内容,找到在网页中的其它链接地址,然后通过这些链接地址寻找下一个网页,这样一直循环下去,直到把这个网站所有的网页都抓取完为止。
如果把整个互联网当成一个网站,那么网络蜘蛛就可以用这个原理把互联网上所有的网页都抓取下来。这样看来,网络爬虫就是一个爬行程序,一个抓取网页的程序。网络爬虫的基本操作是抓取网页。
1.2.3 爬虫的工作过程
在用户浏览网页的过程中,我们可能会看到许多好看的图片,比如 http://image.baidu.com/ ,我们会看到几张的图片以及百度搜索框,这个过程其实就是用户输入网址之后,经过 DNS 服务器,找到服务器主机,向服务器发出一个请求,服务器经过解析之后,发送给用户的浏览器 HTML、JS、CSS 等文件,浏览器解析出来,用户便可以看到形形色色的图片了。
因此,用户看到的网页实质是由 HTML 代码构成的,爬虫爬来的便是这些内容,通过分析和过滤这些 HTML 代码,实现对图片、文字等资源的获取。
想象你是一只蜘蛛,现在你被放到了互联“网”上。那么,你需要把所有的网页都看一遍。怎么办呢?没问题呀,你就随便从某个地方开始,比如说人民日报的首页,我们用$表示吧。
在人民日报的首页,你看到那个页面引向的各种链接。于是你很开心地从爬到了“国内新闻”那个页面。太好了,这样你就已经爬完了俩页面(首页和国内新闻)!暂且不用管爬下来的页面怎么处理的,你就想象你把这个页面完完整整抄成了个html放到了你身上。
突然你发现, 在国内新闻这个页面上,有一个链接链回“首页”。作为一只聪明的蜘蛛,你肯定知道你不用爬回去的吧,因为你已经看过了啊。所以,你需要用你的脑子,存下你已经看过的页面地址。这样,每次看到一个可能需要爬的新链接,你就先查查你脑子里是不是已经去过这个页面地址。如果去过,那就别去了。
好的,理论上如果所有的页面可以从首页达到的话,那么可以证明你一定可以爬完所有的网页。但是为什么爬虫事实上是个非常复杂的东西——搜索引擎公司通常有一整个团队来维护和开发。
因为就现在网络资源的大小而言,即使很大的搜索引擎也只能获取网络上可得到资源的一小部分。2001年由劳伦斯河盖尔斯共同做的一项研究指出,没有一个搜索引擎抓取的内容达到网络的16%。网络爬虫通常仅仅下载网页内容的一部分,并且不管你的带宽有多大,只要你的机器下载网页的速度是瓶颈的话,那么你只有加快这个速度。那么我们就需要提高效率——使用多线程。这就是我们为什么使用Scrapy的原因。
1.3.1 Urlib库的基本使用
1.分分钟扒一个网页下来
怎样扒网页呢?其实就是根据URL来获取它的网页信息,虽然我们在浏览器中看到的是一幅幅优美的画面,但是其实是由浏览器解释才呈现出来的,实质它是一段 HTML 代码,加 JS、CSS,如果把网页比作一个人,那么 HTML 便是他的骨架,JS 便是他的肌肉,CSS 便是它的衣服。所以最重要的部分是存在于 HTML 中的,下面我们就写个例子来扒一个网页下来。代码如图1-4所示。
是的你没看错,真正的程序就两行,把它保存成 demo.py,进入该文件的目录,执行如下命令查看运行结果,感受一下,使用命令python demo.py,运行效果如图1-5所示。
看,这个网页的源码已经被我们扒下来了,是不是很酸爽?
2. 分析扒网页的方法
那么我们来分析这两行代码,第一行
首先我们调用的是 urllib2 库里面的 urlopen 方法,传入一个 URL,这个网址是百度首页,协议是 HTTP 协议,当然你也可以把 HTTP 换做 FTP,FILE,HTTPS 等等,只是代表了一种访问控制协议,urlopen 一般接受三个参数,它的参数如下:
第一个参数 url 即为 URL,第二个参数 data 是访问 URL 时要传送的数据,第三个 timeout是设置超时时间。
第二三个参数是可以不传送的,data 默认为空 None,timeout 默认为 socket._GLOBAL_DEFAULT_TIMEOUT
第一个参数 URL 是必须要传送的,在这个例子里面我们传送了百度的 URL,执行 urlopen 方法之后,返回一个 response 对象,返回信息便保存在这里面。
response 对象有一个 read 方法,可以返回获取到的网页内容。
如果不加 read 直接打印会是什么?答案如下:
直接打印出了该对象的描述,所以记得一定要加 read 方法,否则它不出来内容可就不怪我咯!
3. 构造Request
其实上面的 urlopen 参数可以传入一个 request 请求,它其实就是一个 Request 类的实例,构造时需要传入 Url,Data 等等的内容。比如上面的两行代码,我们可以这么改写
运行结果是完全一样的,只不过中间多了一个 request 对象,推荐大家这么写,因为在构建请求时还需要加入好多内容,通过构建一个 request,服务器响应请求得到应答,这样显得逻辑上清晰明确。
4. POST 和 GET 数据传送
上面的程序演示了最基本的网页抓取,不过,现在大多数网站都是动态网页,需要你动态地传递参数给它,它做出对应的响应。所以,在访问时,我们需要传递数据给它。最常见的情况是什么?对了,就是登录注册的时候呀。
把数据用户名和密码传送到一个 URL,然后你得到服务器处理之后的响应,这个该怎么办?下面让我来为小伙伴们揭晓吧!
数据传送分为 POST 和 GET 两种方式,两种方式有什么区别呢?
最重要的区别是 GET 方式是直接以链接形式访问,链接中包含了所有的参数,当然如果包含了密码的话是一种不安全的选择,不过你可以直观地看到自己提交了什么内容。POST 则不会在网址上显示所有的参数,不过如果你想直接查看提交了什么就不太方便了,大家可以酌情选择。
POST 方式:
上面我们说了 data 参数是干嘛的?对了,它就是用在这里的,我们传送的数据就是这个参数data,下面演示一下 POST 方式,代码如图1-6所示。
我们引入了 urllib 库,现在我们模拟登陆 CSDN,当然上述代码可能登陆
注意上面字典的定义方式还有一种,写法是等价的,如图1-7所示。
以上方法便实现了 POST 方式的传送
GET 方式:
至于 GET 方式我们可以直接把参数写到网址上面,直接构建一个带参数的 URL 出来即可,如图1-8所示。
你可以 print geturl,打印输出一下 url,发现其实就是原来的 url 加?然后加编码后的参数
http://passport.csdn.net/account/login?username=1016903103%40qq.com&password=XXXX
和我们平常 GET 访问方式一模一样,这样就实现了数据的 GET 方式传送。
1.3.2 Urlib库的高级用法
1.设置 Headers
有些网站不会同意程序直接用上面的方式进行访问,如果识别有问题,那么站点根本不会响应,所以为了完全模拟浏览器的工作,我们需要设置一些 Headers 的属性。
首先,打开我们的浏览器,调试浏览器 F12,我用的是 Chrome,打开网络监听,示意如下,比如知乎,点登录之后,我们会发现登陆之后界面都变化了,出现一个新的界面,实质上这个页面包含了许许多多的内容,这些内容也不是一次性就加载完成的,实质上是执行了好多次请求,一般是首先请求HTML文件,然后加载 JS,CSS 等等,经过多次请求之后,网页的骨架和肌肉全了,整个网页的效果也就出来了,操作效果如图1-9所示。
拆分这些请求,我们只看一第一个请求,你可以看到,有个 Request URL,还有 headers,下面便是 response,图片显示得不全,小伙伴们可以亲身实验一下。那么这个头中包含了许许多多是信息,有文件编码啦,压缩方式啦,请求的 agent 啦等等。
其中,agent 就是请求的身份,如果没有写入请求身份,那么服务器不一定会响应,所以可以在 headers 中设置agent,例如下面的例子,这个例子只是说明了怎样设置的 headers,小伙伴们看一下设置格式就好,如图1-10所示。
这样,我们设置了一个 headers,在构建 request 时传入,在请求时,就加入了 headers 传送,服务器若识别了是浏览器发来的请求,就会得到响应。
另外,我们还有对付”反盗链”的方式,对付防盗链,服务器会识别 headers 中的 referer 是不是它自己,如果不是,有的服务器不会响应,所以我们还可以在 headers 中加入 referer
例如我们可以构建下面的headers,代码如图1-11所示。
同上面的方法,在传送请求时把 headers 传入 Request 参数里,这样就能应付防盗链了。
另外 headers 的一些属性,下面的需要特别注意一下:
Ø User-Agent : 有些服务器或 Proxy 会通过该值来判断是否是浏览器发出的请求
Ø Content-Type : 在使用 REST 接口时,服务器会检查该值,用来确定 HTTP Body 中的内容该怎样解析。
Ø application/xml : 在 XML RPC,如 RESTful/SOAP 调用时使用
Ø application/json : 在 JSON RPC 调用时使用
Ø application/x-www-form-urlencoded : 浏览器提交 Web 表单时使用
在使用服务器提供的 RESTful 或 SOAP 服务时, Content-Type 设置错误会导致服务器拒绝服务,其他的有必要的可以审查浏览器的 headers 内容,在构建时写入同样的数据即可。
2. Proxy(代理)的设置
urllib2 默认会使用环境变量 http_proxy 来设置 HTTP Proxy。假如一个网站它会检测某一段时间某个IP 的访问次数,如果访问次数过多,它会禁止你的访问。所以你可以设置一些代理服务器来帮助你做工作,每隔一段时间换一个代理,网站君都不知道是谁在捣鬼了,这酸爽!
下面一段代码说明了代理的设置用法,如图1-12所示。
3.Timeout设置
上一节已经说过 urlopen 方法了,第三个参数就是 timeout 的设置,可以设置等待多久超时,为了解决一些网站实在响应过慢而造成的影响。
例如下面的代码,如果第二个参数 data 为空那么要特别指定是 timeout 是多少,写明形参,如果data已经传入,则不必声明,代码如图1-13和1-14所示。
4.使用 HTTP 的 PUT 和 DELETE 方法
http 协议有六种请求方法,get,head,put,delete,post,options,我们有时候需要用到 PUT 方式或者 DELETE 方式请求。
PUT:这个方法比较少见。HTML 表单也不支持这个。本质上来讲, PUT 和 POST 极为相似,都是向服务器发送数据,但它们之间有一个重要区别,PUT 通常指定了资源的存放位置,而 POST 则没有,POST 的数据存放位置由服务器自己决定。
DELETE:删除某一个资源。基本上这个也很少见,不过还是有一些地方比如amazon的S3云服务里面就用的这个方法来删除资源。 如果要使用 HTTP PUT 和 DELETE ,只能使用比较低层的 httplib 库。虽然如此,我们还是能通过下面的方式,使 urllib2 能够发出 PUT 或DELETE 的请求,不过用的次数的确是少,在这里提一下。
操作代码,如图1-14所示。
5.使用 DebugLog
可以通过下面的方法把 Debug Log 打开,这样收发包的内容就会在屏幕上打印出来,方便调试,这个也不太常用,仅提一下。
1.3.3 URLError异常处理
大家好,本节在这里主要说的urlerror异常处理,是还有 HTTPError,以及对它们的一些处理。
1.URLError
首先解释下 URLError 可能产生的原因:
Ø 网络无连接,即本机无法上网
Ø 连接不到特定的服务器
Ø 服务器不存在
在代码中,我们需要用 try-except 语句来包围并捕获相应的异常。下面是一个例子,先感受下它的风骚,代码如图1-16所示。
我们利用了 urlopen 方法访问了一个不存在的网址,运行结果如下图1-17所示。
它说明了错误代号是11004,错误原因是 getaddrinfo failed。
2.HTTPError
HTTPError 是 URLError 的子类,在你利用 urlopen 方法发出一个请求时,服务器上都会对应一个应答对象 response,其中它包含一个数字”状态码”。举个例子,假如 response 是一个”重定向”,需定位到别的地址获取文档,urllib2 将对此进行处理。
其他不能处理的,urlopen 会产生一个 HTTPError,对应相应的状态吗,HTTP 状态码表示HTTP 协议所返回的响应的状态。下面将状态码归结如下图1-18所示:
HTTPError 实例产生后会有一个 code 属性,这就是是服务器发送的相关错误号。 因为 urllib2 可以为你处理重定向,也就是 3 开头的代号可以被处理,并且 100-299 范围的号码指示成功,所以你只能看到 400-599 的错误号码。
下面我们写一个例子来感受一下,捕获的异常是 HTTPError,它会带有一个 code 属性,就是错误代号,另外我们又打印了 reason 属性,这是它的父类 URLError 的属性,代码如图1-19所示。
运行结果如下图1-20所示。
错误代号是 403,错误原因是 Forbidden,说明服务器禁止访问。
我们知道,HTTPError 的父类是 URLError,根据编程经验,父类的异常应当写到子类异常的后面,如果子类捕获不到,那么可以捕获父类的异常,所以上述的代码可以这么改写,如图1-21所示。
首先对异常的属性进行判断,以免出现属性输出报错的现象。
以上,就是对 URLError 和 HTTPError 的相关介绍,以及相应的错误处理办法。
1.3.4Cookie的使用
大家好哈,上一节我们研究了一下爬虫的异常处理问题,那么接下来我们一起来看一下 Cookie 的使用。
为什么要使用 Cookie 呢?
Cookie,指某些网站为了辨别用户身份、进行 session 跟踪而储存在用户本地终端上的数据(通常经过加密)
比如说有些网站需要登录后才能访问某个页面,在登录之前,你想抓取某个页面内容是不允许的。那么我们可以利用 Urllib2 库保存我们登录的 Cookie,然后再抓取其他页面就达到目的了。
在此之前呢,我们必须先介绍一个 opener 的概念。
1. Opener
当你获取一个 URL 你使用一个 opener(一个 urllib2.OpenerDirector 的实例)。在前面,我们都是使用的默认的 opener,也就是 urlopen。它是一个特殊的 opener,可以理解成opener 的一个特殊实例,传入的参数仅仅是 url,data,timeout。
如果我们需要用到 Cookie,只用这个 opener 是不能达到目的的,所以我们需要创建更一般的opener 来实现对 Cookie 的设置。
2. Cookielib
cookielib 模块的主要作用是提供可存储 cookie 的对象,以便于与 urllib2 模块配合使用来访问 Internet 资源。Cookielib 模块非常强大,我们可以利用本模块的 CookieJar 类的对象来捕获 cookie 并在后续连接请求时重新发送,比如可以实现模拟登录功能。该模块主要的对象有 CookieJar、FileCookieJar、MozillaCookieJar、LWPCookieJar。
它们的关系:CookieJar —-派生—->FileCookieJar —-派生—–>MozillaCookieJar 和LWPCookieJar
3. 获取 Cookie 保存到变量
首先,我们先利用 CookieJar 对象实现获取 cookie 的功能,存储到变量中,参考代码如下,如图1-22所示。
我们使用以上方法将 cookie 保存到变量中,然后打印出了 cookie 中的值,运行结果如下,如图1-23所示。
4. 保存 Cookie 到文件
在上面的方法中,我们将 cookie 保存到了 cookie 这个变量中,如果我们想将 cookie 保存到文件中该怎么做呢?这时,我们就要用到FileCookieJar 这个对象了,在这里我们使用它的子类 MozillaCookieJar 来实现 Cookie的保存,实现代码如图1-24所示。
由此可见,ignore_discard 的意思是即使 cookies 将被丢弃也将它保存下来,ignore_expires 的意思是如果在该文件中 cookies 已经存在,则覆盖原文件写入,在这里,我们将这两个全部设置为 True。运行之后,cookies 将被保存到 cookie.txt文件中,我们查看一下内容,如下图1-25所示。
5. 从文件中获取 Cookie 并访问
那么我们已经做到把 Cookie 保存到文件中了,如果以后想使用,可以利用下面的方法来读取cookie 并访问网站,感受一下,代码如图1-26所示。
设想,如果我们的 cookie.txt 文件中保存的是某个人登录百度的 cookie,那么我们提取出这个 cookie 文件内容,就可以用以上方法模拟这个人的账号登录百度。
6. 利用 cookie 模拟网站登录
下面我们以我们学校的教育系统为例,利用 cookie 实现模拟登录,并将 cookie 信息保存到文本文件中,来感受一下 cookie 大法吧!实现代码如下图1-27所示。
以上程序的原理如下
创建一个带有 cookie 的 opener,在访问登录的 URL 时,将登录后的 cookie 保存下来,然后利用这个 cookie 来访问其他网址。
如登录之后才能查看的成绩查询呀,本学期课表呀等等网址,模拟登录就这么实现啦,是不是很酷炫?
好,小伙伴们要加油哦!我们现在可以顺利获取网站信息了,接下来就是把网站里面有效内容提取出来,下一节我们去会会正则表达式!
1.3.5正则表达式
在前面我们已经搞定了怎样获取页面的内容,不过还差一步,这么多杂乱的代码夹杂文字我们怎样把它提取出来整理呢?下面就开始介绍一个十分强大的工具,正则表达式!
1. 了解正则表达式
正则表达式是对字符串操作的一种逻辑公式,就是用事先定义好的一些特定字符、及这些特定字符的组合,组成一个“规则字符串”,这个“规则字符串”用来表达对字符串的一种过滤逻辑。 正则表达式是用来匹配字符串非常强大的工具,在其他编程语言中同样有正则表达式的概念,Python同样不例外,利用了正则表达式,我们想要从返回的页面内容提取出我们想要的内容就易如反掌了。
正则表达式的大致匹配过程是:
1) 依次拿出表达式和文本中的字符比较,
2) 如果每一个得到字符都能匹配,则匹配9功;一旦有匹配不成功的字符则匹配失败。
3) 如果表达式中有量词或边界,这个过程会稍微有一些不同。
2. 正则表达式的语法规则
下面是 Python 中正则表达式的一些匹配规则,如图1-28所示
3. 数量词的贪婪模式与非贪婪模式
正则表达式通常用于在文本中查找匹配的字符串。Python 里数量词默认是贪婪的(在少数语言里也可能是默认非贪婪),总是尝试匹配尽可能多的字符;非贪婪的则相反,总是尝试匹配尽可能少的字符。例如:正则表达式 ”ab” 如果用于查找 ”abbbc”,将找到 ”abbb”。而如果使用非贪婪的数量词 ”ab?”,将找到 ”a”。
注:我们一般使用非贪婪模式来提取。
4. 反斜杠问题
与大多数编程语言相同,正则表达式里使用”\”作为转义字符,这就可能造成反斜杠困扰。假如你需要匹配文本中的字符”\”,那么使用编程语言表示的正则表达式里将需要4个反斜杠”\\”:前两个和后两个分别用于在编程语言里转义成反斜杠,转换成两个反斜杠后再在正则表达式里转义成一个反斜杠。
Python 里的原生字符串很好地解决了这个问题,这个例子中的正则表达式可以使用 r”\” 表示。同样,匹配一个数字的 ”\d” 可以写成 r”\d”。有了原生字符串,妈妈也不用担心是不是漏写了反斜杠,写出来的表达式也更直观勒。
5. Python Re 模块
Python 自带了 re 模块,它提供了对正则表达式的支持。主要用到的方法列举如下:
1.返回pattern对象
re.compile(string[,flag])
2.以下为匹配所用函数
re.match(pattern, string[, flags])
re.search(pattern, string[, flags])
re.split(pattern, string[, maxsplit])
re.findall(pattern, string[, flags])
re.finditer(pattern, string[, flags])
re.sub(pattern, repl, string[, count])
re.subn(pattern, repl, string[, count])
在介绍这几个方法之前,我们先来介绍一下 pattern 的概念,pattern 可以理解为一个匹配模式,那么我们怎么获得这个匹配模式呢?很简单,我们需要利用 re.compile 方法就可以。例如
pattern = re.compile(r'hello')
在参数中我们传入了原生字符串对象,通过 compile 方法编译生成一个 pattern 对象,然后我们利用这个对象来进行进一步的匹配。
另外大家可能注意到了另一个参数 flags,在这里解释一下这个参数的含义:
参数 flag 是匹配模式,取值可以使用按位或运算符’|’表示同时生效,比如 re.I | re.M。
可选值有:
Ø re.I(全拼:IGNORECASE): 忽略大小写(括号内是完整写法,下同)
Ø re.M(全拼:MULTILINE): 多行模式,改变'^'和'$'的行为(参见上图)
Ø re.S(全拼:DOTALL): 点任意匹配模式,改变'.'的行为
Ø re.L(全拼:LOCALE): 使预定字符类 \w \W \b \B \s \S 取决于当前区域设定
Ø re.U(全拼:UNICODE): 使预定字符类 \w \W \b \B \s \S \d \D 取决于unicode定义的字符属性
Ø re.X(全拼:VERBOSE): 详细模式。这个模式下正则表达式可以是多行,忽略空白字符,并可以加入注释。
在刚才所说的另外几个方法例如 re.match 里我们就需要用到这个 pattern 了,下面我们一一介绍。
(1) re.match(pattern, string[, flags])
这个方法将会从 string(我们要匹配的字符串)的开头开始,尝试匹配 pattern,一直向后匹配,如果遇到无法匹配的字符,立即返回 None,如果匹配未结束已经到达 string 的末尾,也会返回 None。两个结果均表示匹配失败,否则匹配 pattern 成功,同时匹配终止,不再对string 向后匹配。下面我们通过一个例子理解一下。如图1-29所示
运行结果,如图1-30所示
匹配分析
1) 第一个匹配,pattern 正则表达式为 ’hello’,我们匹配的目标字符串 string 也为hello,从头至尾完全匹配,匹配成功。
2) 第二个匹配,string 为 helloo CQC,从 string 头开始匹配 pattern 完全可以匹配,pattern 匹配结束,同时匹配终止,后面的 o CQC 不再匹配,返回匹配成功的信息。
3) 第三个匹配,string为helo CQC,从 string 头开始匹配 pattern,发现到 ‘o’ 时无法完成匹配,匹配终止,返回 None
4) 第四个匹配,同第二个匹配原理,即使遇到了空格符也不会受影响。
我们还看到最后打印出了 result.group(),这个是什么意思呢?下面我们说一下关于 match 对象的的属性和方法 Match 对象是一次匹配的结果,包含了很多关于此次匹配的信息,可以使用 Match 提供的可读属性或方法来获取这些信息,如图1-31所示。
下面我们用一个例子来体会一下,代码如图1-32所示。
运行结果如图1-33所示。
(2) re.search(pattern, string[, flags])
search 方法与 match 方法极其类似,区别在于 match() 函数只检测 re 是不是在 string的开始位置匹配,search() 会扫描整个 string 查找匹配,match()只有在0位置匹配成功的话才有返回,如果不是开始位置匹配成功的话,match() 就返回 None。同样,search 方法的返回对象同样 match() 返回对象的方法和属性。我们用一个例子感受一下,代码如图1-34所示。
输入出结果为world。
(3) re.split(pattern, string[, flags])
按照能够匹配的子串将 string 分割后返回列表。maxsplit 用于指定最大分割次数,不指定将全部分割。我们通过下面的例子感受一下。
import re
pattern = re.compile(r'\d+')
print re.split(pattern,'one1two2three3four4')
输出结果为:['one', 'two', 'three', 'four', '']
(4) re.findall(pattern, string[, flags])
搜索 string,以列表形式返回全部能匹配的子串。我们通过这个例子来感受一下。
import re
pattern = re.compile(r'\d+')
print re.findall(pattern,'one1two2three3four4')
输出结果为:['1', '2', '3', '4']
(5) re.finditer(pattern, string[, flags])
搜索 string,返回一个顺序访问每一个匹配结果(Match对象)的迭代器。我们通过下面的例子来感受一下。
import re
pattern = re.compile(r'\d+')
for m in re.finditer(pattern,'one1two2three3four4'):
print m.group(),
输出结果为:1 2 3 4
(6) re.sub(pattern, string[, flags])
使用 repl 替换 string 中每一个匹配的子串后返回替换后的字符串。 当 repl 是一个字符串时,可以使用 \id 或 \g、\g 引用分组,但不能使用编号0。 当 repl 是一个方法时,这个方法应当只接受一个参数(Match对象),并返回一个字符串用于替换(返回的字符串中不能再引用分组)。 count 用于指定最多替换次数,不指定时全部替换。
import re
pattern = re.compile(r'(\w+) (\w+)')
s = 'i say, hello world!'
print re.sub(pattern,r'\2 \1', s)
def func(m):
return m.group(1).title() + ' ' + m.group(2).title()
print re.sub(pattern,func, s)
输出结果为:
say i, world hello!
I Say, Hello World!
(7) re.subn(pattern, string[, flags])
返回 (sub(repl, string[, count]), 替换次数)。
import re
pattern = re.compile(r'(\w+) (\w+)')
s = 'i say, hello world!'
print re.subn(pattern,r'\2 \1', s)
def func(m):
return m.group(1).title() + ' ' + m.group(2).title()
print re.subn(pattern,func, s)
输出结果为:
('say i, world hello!', 2)
('I Say, Hello World!', 2)
(8) Python Re 模块的另一种使用方式
在上面我们介绍了7个工具方法,例如 match,search 等等,不过调用方式都是 re.match,re.search 的方式,其实还有另外一种调用方式,可以通过 pattern.match,pattern.search 调用,这样调用便不用将 pattern 作为第一个参数传入了,大家想怎样调用皆可。
函数 API 列表,代码如图1-35所示。
具体的调用方法不必详说了,原理都类似,只是参数的变化不同。小伙伴们尝试一下吧~
小伙伴们加油,即使这一节看得云里雾里的也没关系,接下来我们会通过一些实战例子来帮助大家熟练掌握正则表达式的。