智一面的面试题提供python爬虫的测试题
使用地址:
http://www.gtalent.cn/exam/interview?token=cbcffb5e948ad6b86628ce74ea08ecb8
一、起源与发展
我们在学习一门技术的时候有必要了解其起源与发展过程,这对我们去理解技术本身有一定的帮助!20世纪40年代:正则表达式最初的想法来自两位神经学家:沃尔特·皮茨与麦卡洛克,他们研究出了一种用数学方式来描述神经网络的模型。1956年:一位名叫Stephen Kleene的数学科学家发表了一篇题目是《神经网事件的表示法》的论文,利用称之为正则集合的数学符号来描述此模型,引入了正则表达式的概念。正则表达式被作为用来描述其称之为“正则集的代数”的一种表达式,因而采用了“正则表达式”这个术语。1968年:C语言之父、UNIX之父肯·汤普森把这个“正则表达式”的理论成果用于做一些搜索算法的研究,他描述了一种正则表达式的编译器,于是出现了应该算是最早的正则表达式的编译器qed(这也就成为后来的grep编辑器)。Unix使用正则之后,正则表达式不断的发展壮大,然后大规模应用于各种领域,根据这些领域各自的条件需要,又发展出了许多版本的正则表达式,出现了许多的分支。我们把这些分支叫做“流派”。1987年:Perl语言诞生了,它综合了其他的语言,用正则表达式作为基础,开创了一个新的流派,Perl流派。之后很多编程语言如:Python、Java、Ruby、.Net、PHP等等在设计正则式支持的时候都参考Perl正则表达式二、语法
完整的正则表达式由两种字符构成:特殊字符(元字符)和普通字符。ps:元字符表示正则表达式功能的最小单位,如*
^
$
\d
等等三、匹配原理
1.执行过程
正则表达式的执行,是由正则表达引擎编译执行的,大致的执行流程猪哥也画了个流程图给大家看看。这里给大家提一点就是:预编译(pre-use compile)建议大家在生产环境中使用预编译功能,为什么呢?以Python语言内置re
模块举例:-
通过
re.compile(pattern)
预编译返回Pattern对象,在后面代码中可以直接引用。 -
通过
re.match(pattern, text)
即用编译,虽然也会有缓存Pattern对象,但是每次使用都需要去缓存中取出,比预编译多一步取操作。
pattern = r'http:\/\/(?:.?\w+)+'
text = '<a href="http://www.xxx.com">xxx.com</a>'
2.引擎(重点)
既然正则表达式由执行引擎执行,那我们就来讲讲正则表达式的引擎吧,这一块是重点,希望大家仔细看看,弄懂了理解了才行!正则引擎主要可以分为基本不同的两大类:- DFA (Deterministic finite automaton) 确定型有穷自动机
- NFA (Non-deterministic finite automaton) 非确定型有穷自动机
确定型
、有穷
、自动机
这几个名词:- 确定型与非确定型:假设有一个字符串(text=abc)需要匹配,在没有编写正则表达式的前提下,就直接可以确定字符匹配顺序的就是确定型,不能确定字符匹配顺序的则为非确定型。
- 有穷:有穷即表示有限的意思,这里表示有限次数内能得到结果。
- 自动机:自动机便是自动完成,在我们设置好匹配规则后由引擎自动完成,不需要人为干预!
- 文本主导:按照文本的顺序执行,这也就能说明为什么DFA引擎是确定型(deterministic)了,稳定!
-
记录当前有效的所有可能:我们看到当执行到
(d|b)
时,同时比较表达式中的d
和b
,所以会需要更多的内存。 - 每个字符只检查一次:这提高了执行效率,而且速度与正则表达式无关。
- 不能使用反向引用等功能:因为每个字符只检查一次,文本零宽度(位置)只记录当前比较值,所以不能使用反向引用、环视等一些功能!
NFA引擎执行原理:
NFA引擎的一些特点:
- 文表达式主导:按照表达式的一部分执行,如果不匹配换其他部分继续匹配,直到表达式匹配完成。
-
会记录某个位置:我们看到当执行到
(d|b)
时,NFA引擎会记录字符的位置(零宽度),然后选择其中一个先匹配。 -
单个字符可能检查多次:我们看到当执行到
(d|b)
时,比较d
后发现不匹配,于是NFA引擎换表达式的另一个分支b
,同时文本位置回退,重新匹配字符'b'。这也是NFA引擎是非确定型的原因,同时带来另一个问题效率可能没有DFA引擎高。 - 可实现反向引用等功能:因为具有回退这一步,所以可以很容易的实现反向引用、环视等一些功能!
3.回溯(重点)
作为绝大多数编程语言都选择的引擎——NFA (非确定型有穷自动机) 引擎,我们当然要再详细了解一下它的精髓——回溯。回溯的原理类似我们走迷宫时走过的路设置一个标志物,如果不对则原路返回,换另一条路。回溯机制不但需要重新计算正则表达式和文本的对应位置,也需要维护括号内的子表达式所匹配文本的状态(b匹配成功),保存到内存中以数字编号的组中,这就叫捕获组。保存括号内的匹配结果之后,我们在后面的正则表达式中就可以使用,这就是我们所说的反向引用,在上面的案例中只有一个捕获,所以$1=b
。回溯陷阱:讲到回溯必须提到回溯陷阱,它导致的结果就是机器CPU使用率爆满(超100%),机器就卡死了。举个例子:text=aaaaa,pattern=/^(a*)b$/,匹配过程大致是- (a*):匹配到了文本中的aaaaa
- 匹配正则中的b,但是失败,因为(a*)已经把text都吃了
- 这时候引擎会要求(a*)吐出最后一个字符(a),但是无法匹配b
- 第二次是吐出倒数第二个字符(还是a),依然无法匹配
- 就这样引擎会要求(a*)逐个将吃进去的字符都吐出来
- 但是到最后都无法匹配b
*
匹配的东西一点一点吐回,我们假设如果文本长度为几万,那引擎就要回溯几万次,这对机器的CPU来说简直是灾难。有些复杂的正则表达式可能有多个部分都要回溯,那回溯次数就是指数型。如果文本长度为500,一个表达式有两部分都要回溯,那次数可能是500^2=25万次,这谁受得了!关于更多更详细的回溯介绍,推荐大家可以阅读《精通正则表达式》这本书! 四、优化
编写巧妙的正则表达式不仅仅是一种技能,而且还是一种艺术。上面我们了解到,绝大多数的编程语言都采用的是NFA引擎,而NFA引擎的特点是:功能强大、但有回溯机制所以效率慢。所以我们需要学习一些NFA引擎的一些优化技巧,以减少引擎回溯次数以及更直接的匹配到结果!针对NFA引擎的可优化的点其实挺多的,为了方便大家记忆,猪哥也画幅结构图归纳一下,方便大家收藏细看。在面试过程中也许会被问到关于正则的优化,大家记住几点就可以。
智一面的面试题提供python的测试题
使用地址:python工程师(爬虫)
http://www.gtalent.cn/exam/interview?token=cbcffb5e948ad6b86628ce74ea08ecb8