在爬虫采集数据的过程中,如何定位及匹配数据是必须解决的一项任务。最常用的定位方式有三种:XPATH,CSS选择器及正则表达式。以下我们来系统的学习他们。
一、XPATH 语法
参考文档:XPATH教程 from runoob
1. Xpath是什么
XPath是一个W3C标准,可以供XSLT、XPointer以及其他XML解析软件使用;它包含一个标准函数库;使用路径表达式在XML文档中进行导航。在爬虫学习中,我们最需要了解的就是最后一项:如何使用XPath路径表达式进行导航。在此之前,首先让我们学习下XPath的基本概念之一:节点。
2. XPath节点及相关
XPath中有七种类型的节点:元素,属性,文本,命名空间,处理指令,注释以及文档(根)节点。除了节点之外,还有基本值(Atomic value)的概念。
基本值:无父或无子的节点。从定义上看,基本值是一种特殊节点,常见于文本。
项目(Item):基本值或者节点。
XML是被作为文档树来对待的,也因此会涉及根节点、父子节点等概念,下面让我们总结下节点关系。
3. XPath节点关系
父(Parent): 每个元素或属性都有一个父,即其直接上级节点。
子(Children):元素节点可以有0、1或多个子,即其直接下级节点。
兄弟(Sibling):拥有相同的父的节点或节点集合。
先辈(Ancestor):某节点的父,父的父,以此类推十八代(虚指)。
后代(Descendant):某节点的子,子的子,以此类推。
了解了基本概念,接下来是语法。
4. XPath语法
4.1 节点选取
nodename 选取当前节点的所有nodename子节点;
/ 从根节点选取,即绝对路径;
// 从当前节点选取,即相对路径;
. 选取当前节点;
.. 选取当前节点的父节点;
@ 选取属性。
4.2 谓语(Predicates)
结合实例解释如下:
/bookstore/book[1] 选取bookstore子元素的第一个book元素;
/bookstore/book[last()] 选取bookstore子元素的最后一个book元素;
/bookstore/book[position()<3] 选取bookstore元素的前两个book元素;
*之前提到XPath中包含一个标准函数库;last()和position()就是其中常见的例子。
//title[@lang] 选取拥有lang属性的title元素;
//title[@lang='eng'] 选取lang属性为'eng'的title元素;
/bookstore/book[price>35.00] 选取bookstore元素中的所有book元素,且其中的price元素大于35.00。
4.3 通配符
* 匹配任何元素节点; @*匹配任何属性节点;node()匹配任何类型的节点。
4.4 路径组合
//book/title | //book/price 选取book元素的所有title和price元素。
之前我们介绍了节点关系的概念,那么如何利用节点关系来定位节点呢,请看XPath轴。
5. XPath轴(Axes)
ancestor 选取当前节点的所有先辈;
ancestor-or-self 选取当前节点及其所有先辈;
descendant 选取当前节点的所有后辈;
descendant-or-self 选取当前节点及其所有后辈;
parent 选取当前节点的父节点;
child 选取当前节点的子节点;
following 选取当前节点之后的所有节点;
following-sibling 选取当前节点之后的所有兄弟节点;
preceding 选取当前节点之前的所有节点;
preceding-sibling 选取当前节点之前的所有兄弟节点;
self 选取当前节点;
attribute 选取当前节点所有属性;
namespace 选取当前节点的所有命名空间节点。
用法,比如要查找<a>元素之后的<span>元素,使用'//a/following::span'。
6. XPath运算符
| 计算两个节点集;
+ - * div mod加减乘除求余;
= != < <= > >= 比较;
or and 或与。
二、CSS选择器语法
参考资料:CSS选择器 from w3school
在定位元素时,CSS选择器和XPath经常可以相互替代。常用的CSS选择器如下:
.class 根据class属性选择元素;
#id 根据id属性选择元素;
* 选择所有元素;
element 选择所有element元素;
element,element1 选择所有element及element1元素;
element element1 选择element内部的所有element1元素;
element>element1 选择element的element1子元素;
element+element1 选择紧接在element之后的element1元素;
[attribute] 选择带有attribute属性的所有元素;
[attribute=value] 选择attribute属性值为value的所有元素;
[attribute~=value] 选择attribute属性值包含value的所有元素;
[attribute|=value] 选择attribute属性值以value开头的所有元素;
a:link 选择所有未被访问的链接;a:visited 选择所有已被访问链接;a:hover 选择活动链接;
element:focus 选择获得焦点的element元素;
element:first-letter 选择element元素首字母;element:first-line 选择element元素首行;
element:first-child 选择属于element父元素的第一个子元素的每个element元素;
element:lang(language) 选择带有以language开头的lang属性值得每个element元素;
ele1~ele2 选择前面有ele1元素的每个ele2元素;
ele[attribute^=value] 选择attribute属性值以value开头的每个ele元素;
ele[attribute$=value] 选择attribute属性值以value结尾的每个ele元素;
ele[attribute*=value] 选择attribute属性值包含value的每个ele元素;
ele:first-of-type 选择属于其父元素的首个ele元素的每个ele元素;
ele:last-of-type 选择属于其父元素的最后ele元素的每个ele元素;
ele:only-of-type 选择属于其父元素唯一的ele元素的每个ele元素;
ele:only-child 选择属于其父元素的唯一子元素的每个ele元素;
ele:nth-child(n) 选择属于其父元素的第n个子元素的每个ele元素;
ele:nth-last-child(n) 选择属于其父元素的倒数第n个子元素的每个ele元素;
ele:nth-of-type(n) 选择属于其父元素的第n个元素的每个ele元素;
ele:nth-last-of-type(n) 选择属于其父元素的倒数第n个元素的每个ele元素;
ele:last-child 选择属于其父元素最后一个子元素的每个ele元素;
:root 选择文档根元素;
ele:empty 选择没有子元素的每个ele元素;
ele:target 选择当前活动的ele元素;
ele:enabled 选择每个启用的ele元素;ele:disabled 选择每个禁用的ele元素;
ele:checked 选择每个被选中的ele元素;
:not(selector) 选择非selector的每个元素;
::selection 选择被用户选取的元素部分。
三、正则表达式语法
参考教程:正则表达式 from runoob
当我们使用word或text之类的办公软件时,经常用到搜索和筛选来定位查找词语。这个功能很便利,但是它的不足之处也很明显。其中之一就是:作为过滤器的模式非常有限。相比之下,正则表达式可以实现前者的所有工作,而且更加强大和灵活。下面让我来看看,它是如何做到这一点的。
1. 正则表达式记号
普通字符 大小写字母,所有数字,所有标点和一些其他字符(如非打印字符)。
非打印字符
\cx 匹配由x指明的控制字符;x的值必须为A-Za-z之一;否则c将被视为字母c;
\f 匹配换页符;\n 匹配换行符;\r 匹配回车符; \s 匹配任何空白字符,包括空格、制表符、换页符等;\S 匹配任何非空白字符;\t 匹配任何制表符;\v 匹配垂直制表符。
特殊字符
^ 匹配开始位置,在方括号中时,表示否定,即不接受该字符集合;$ 匹配结尾位置;
* 匹配前面的子表达式0或多次; + 匹配前面的子表达式1或多次;
? 匹配前面的子表达式0或1次,或指定一个非贪婪限定符;
. 匹配除换行符\n之外的任何单字符;
\ 转义字符;
| 指明两项之间的其中一个。
() 标记一个子表达式的开始和结束位置。子表达式可以获得,供以后使用;
[] 标记一个中括号表达式的开始;
{ } 标记限定符表达式的开始;
限定符
* + ? 见特殊字符;
{n} 匹配确定的n次;{n,} 至少匹配n次;{n,m} 匹配n到m次。
定位符
^ $ 见特殊字符;
\b 匹配字边界(字与空格间的位置);\B 非字符边界
子表达式
前面提到,()可以标记一个子表达式,且该表达式可以被捕获缓存。如果这并不是你想要的,那么可以使用?:消除这种副作用,即将子表达式标记为非捕获元。
除此之外,还有另外两个很重要的非捕获元?=和?!,前者称为正向预查,后者称为反向预查。两者还经常被称为零宽断言(正向、反向);此外还有逆序正向预查和逆序反向预查。
正向预查(?=exp) a. 当单词同时满足前部匹配条件和exp时,返回前部匹配; b. exp非捕获
举例: \b\w+(?=ing\b) 输入 singing and dancing 输出 sing和danc
反向预查(?!exp) a. 当单词满足前部匹配条件且不满足exp时,返回前部匹配,b. exp非捕获
逆序正向预查(?<=exp) a. 当单词同时满足exp和后部匹配条件时,返回后部匹配;b. exp非捕获;
逆序反向预查(?<!exp) a. 当单词不满足exp且满足后部匹配条件时,返回后部匹配;b. exp非捕获;
*名字很拗口,可以这么理解:正向、反向指的是要匹配的字符是在前面还是后面;正序、逆序指的是肯定还是否定子表达式。
引用
每个被捕获的子表达式都会存储在缓冲区,且被编号为1-99。每个缓冲区可以通过\n来访问,其中n为标识特定缓冲区的一位或两位十进制数。最简单,最有用的应用为查找文本中两个相同的相邻单词。
2. 元字符
\ ^ $ * + ? {n} {n,} {n,m} (pattern) (?:pattern) (?=pattern) (?!pattern) x|y [xyz] [^xyz] [a-z] [^a-z]
\b \B \cx \f \n \r \s \S \t \v
以上这些已经介绍过了,下面再看些常用的元字符。
\d 匹配一个数字字符,即[0-9];\D 匹配一个非数字字符;
\w 匹配包括下划线的任何单词字符,即[A-Za-z0-9];
\W 匹配任何非单词字符;
\xn 匹配n,其中n为十六进制,必须为确定的两个数字;
\num 如果前面至少num个被捕获子表达式,则表示引用;否则,如果num为0-7的数字,则匹配八进制;
\un 匹配n,其中n是四个十六进制数字表示的字符。
了解了这么多的符号和运算,很容易想到一个问题,这些运算符的优先级是怎么样的?
3. 运算符优先级
优先级从高到低为:
\ 转义符;
(),[] 子表达式和中括号表达式;
*,+,?,{} 限定符;
^,$,\任何元字符,任何字符 定位点和普通字符;
| 选择。