XPath
xpath(XML Path Language)是一门在XML和HTML文档中查找信息的语言,可用来在XML和HTML文档中对元素和属性进行遍历。
XPath开发工具:
-
- Chrome插件XPath Helper。
- Firefox插件Try XPath。
XPath语法:
选取节点:
XPath 使用路径表达式来选取 XML 文档中的节点或者节点集。这些路径表达式和我们在常规的电脑文件系统中看到的表达式非常相似。
表达式 | 描述 | 示例 | 结果 |
---|---|---|---|
nodename | 选取此节点的所有子节点 | bookstore | 选取bookstore下所有的子节点 |
/ | 如果是在最前面,代表从根节点选取。否则选择某节点下的某个节点 | /bookstore | 选取根元素下所有的bookstore节点 |
// | 从全局节点中选择节点,随便在哪个位置 | //book | 从全局节点中找到所有的book节点 |
@ | 选取某个节点的属性 | //book[@price] | 选择所有拥有price属性的book节点 |
. | 当前节点 | ./a | 选取当前节点下的a标签 |
谓语:
谓语用来查找某个特定的节点或者包含某个指定的值的节点,被嵌在方括号中。
在下面的表格中,我们列出了带有谓语的一些路径表达式,以及表达式的结果:(下标从1开始)
路径表达式 | 描述 |
---|---|
/bookstore/book[1] | 选取bookstore下的第一个子元素 |
/bookstore/book[last()] | 选取bookstore下的最后一个book元素。 |
bookstore/book[position()<3] | 选取bookstore下前面两个子元素。 |
//book[@price] | 选取拥有price属性的book元素 |
//book[@price=10] | 选取所有属性price等于10的book元素 |
通配符:
*表示通配符。
通配符 | 描述 | 示例 | 结果 |
---|---|---|---|
* | 匹配任意节点 | /bookstore/* | 选取bookstore下的所有子元素。 |
@* | 匹配节点中的任何属性 | //book[@*] | 选取所有带有属性的book元素。 |
选取多个路径:
通过在路径表达式中使用“ | ”运算符,可以选取若干个路径。

//bookstore/book | //book/title # 选取所有book元素以及book元素下所有的title元素
运算符:
注意:
/ 和 // 的区别: / 代表只获取直接子节点; //获取子孙节点。 一般// 用的比较多
contains: 有时候某个属性中包含了多个值,那么可以用“contains”函数
谓词中的下标是从1开始的
lxml库
(使用xpath语法来解析xml代码)
lxml 是 一个HTML/XML的解析器,主要的功能是如何解析和提取 HTML/XML 数据。
lxml和正则一样,也是用 C 实现的,是一款高性能的 Python HTML/XML 解析器,我们可以利用之前学习的XPath语法,来快速的定位特定元素以及节点信息。
安装:
lxml python 官方文档:http://lxml.de/index.html
需要安装C语言库,可使用 pip 安装:pip install lxml
基本使用:
我们可以利用他来解析HTML代码,并且在解析HTML代码的时候,如果HTML代码不规范,他会自动的进行补全。

# 使用 lxml 的 etree 库 from lxml import etree text = ''' <div> <ul> <li class="item-0"><a href="link1.html">first item</a></li> <li class="item-1"><a href="link2.html">second item</a></li> <li class="item-inactive"><a href="link3.html">third item</a></li> <li class="item-1"><a href="link4.html">fourth item</a></li> <li class="item-0"><a href="link5.html">fifth item</a> # 注意,此处缺少一个 </li> 闭合标签 </ul> </div> ''' #利用etree.HTML,将字符串解析为HTML文档 html = etree.HTML(text) # 按字符串序列化HTML文档 result = etree.tostring(html) print(result) # 输入结果如下: <html><body> <div> <ul> <li class="item-0"><a href="link1.html">first item</a></li> <li class="item-1"><a href="link2.html">second item</a></li> <li class="item-inactive"><a href="link3.html">third item</a></li> <li class="item-1"><a href="link4.html">fourth item</a></li> <li class="item-0"><a href="link5.html">fifth item</a></li> </ul> </div> </body></html> # 可以看到。lxml会自动修改HTML代码。例子中不仅补全了li标签,还添加了body,html标签。
解析字符串使用 “lxml.etree.HTML”进行解析
注意:
解析出来是一个element对象,后续可以去执行xpath语法
etree.HTML类会去补充标签
从文件中读取html代码:
除了直接使用字符串进行解析,lxml还支持从文件中读取内容。

# 新建一个hello.html 文件 <!-- hello.html --> <div> <ul> <li class="item-0"><a href="link1.html">first item</a></li> <li class="item-1"><a href="link2.html">second item</a></li> <li class="item-inactive"><a href="link3.html"><span class="bold">third item</span></a></li> <li class="item-1"><a href="link4.html">fourth item</a></li> <li class="item-0"><a href="link5.html">fifth item</a></li> </ul> </div> 然后利用etree.parse()方法来读取文件。示例代码如下: from lxml import etree # 读取外部文件 hello.html html = etree.parse('hello.html') result = etree.tostring(html, pretty_print=True) print(result) # 输入结果和之前是相同的。
解析文件使用 “lxml.etree.parser”进行解析。默认的是XML解析器
注意:
etree.parse函数只会去解析,它并不会去补充标签,所以不能去处理那些有问题的标签
可使用etree.HTMLParser解析器去解析HTML代码,该解析器为etree.parse函数的第二个参数
在lxml中使用XPath语法:

获取所有li标签: from lxml import etree html = etree.parse('hello.html') print type(html) # 显示etree.parse() 返回类型 result = html.xpath('//li') print(result) # 打印<li>标签的元素集合 获取所有li元素下的所有class属性的值: from lxml import etree html = etree.parse('hello.html') result = html.xpath('//li/@class') print(result) 获取li标签下href为www.baidu.com的a标签: from lxml import etree html = etree.parse('hello.html') result = html.xpath('//li/a[@href="www.baidu.com"]') print(result) 获取li标签下所有span标签: from lxml import etree html = etree.parse('hello.html') #result = html.xpath('//li/span') #注意这么写是不对的: #因为 / 是用来获取子元素的,而 <span> 并不是 <li> 的子元素,所以,要用双斜杠 result = html.xpath('//li//span') print(result) 获取li标签下的a标签里的所有class: from lxml import etree html = etree.parse('hello.html') result = html.xpath('//li/a//@class') print(result) 获取最后一个li的a的href属性对应的值: from lxml import etree html = etree.parse('hello.html') result = html.xpath('//li[last()]/a/@href') # 谓语 [last()] 可以找到最后一个元素 print(result) 获取倒数第二个li元素的内容: from lxml import etree html = etree.parse('hello.html') result = html.xpath('//li[last()-1]/a') # text 方法可以获取元素内容 print(result[0].text) 获取倒数第二个li元素的内容的第二种方式: from lxml import etree html = etree.parse('hello.html') result = html.xpath('//li[last()-1]/a/text()') print(result)
注意:
使用“xpath”语法,应该使用“element.xpath”方法,来执行xpath的选择
xpath函数返回的永远是列表
针对某一个标签下面再进行xpath时获取子孙元素不能用 "//" ,他会在整个网页当中去寻找, 此时用" .// "
获取文本是通过“xpath”中的“text()”函数
BeautifulSoup4库
和 lxml 一样,Beautiful Soup 也是一个HTML/XML的解析器,主要的功能也是如何解析和提取 HTML/XML 数据。
lxml 只会局部遍历,而Beautiful Soup 是基于HTML DOM(Document Object Model)的,会载入整个文档,解析整个DOM树,因此时间和内存开销都会大很多,所以性能要低于lxml。
BeautifulSoup 用来解析 HTML 比较简单,API非常人性化,支持CSS选择器、Python标准库中的HTML解析器,也支持 lxml 的 XML解析器。
安装:
- 安装:
pip install bs4
- 中文文档:https://www.crummy.com/software/BeautifulSoup/bs4/doc/index.zh.html
简单使用:

from bs4 import BeautifulSoup html = """ <html><head><title>The Dormouse's story</title></head> <body> <p class="title" name="dromouse"><b>The Dormouse's story</b></p> <p class="story">Once upon a time there were three little sisters; and their names were <a href="http://example.com/elsie" class="sister" id="link1"><!-- Elsie --></a>, <a href="http://example.com/lacie" class="sister" id="link2">Lacie</a> and <a href="http://example.com/tillie" class="sister" id="link3">Tillie</a>; and they lived at the bottom of a well.</p> <p class="story">...</p> """ #创建 Beautiful Soup 对象 # 使用lxml来进行解析 soup = BeautifulSoup(html,"lxml") print(soup.prettify())
四个常用的对象:
Beautiful Soup将复杂HTML文档转换成一个复杂的树形结构,每个节点都是Python对象,所有对象可以归纳为4种:
- Tag
- NavigatableString
- BeautifulSoup
- Commen
Tag:
Tag 通俗点讲就是 HTML 中的一个个标签。

from bs4 import BeautifulSoup html = """ <html><head><title>The Dormouse's story</title></head> <body> <p class="title" name="dromouse"><b>The Dormouse's story</b></p> <p class="story">Once upon a time there were three little sisters; and their names were <a href="http://example.com/elsie" class="sister" id="link1"><!-- Elsie --></a>, <a href="http://example.com/lacie" class="sister" id="link2">Lacie</a> and <a href="http://example.com/tillie" class="sister" id="link3">Tillie</a>; and they lived at the bottom of a well.</p> <p class="story">...</p> """ #创建 Beautiful Soup 对象 soup = BeautifulSoup(html,'lxml') print soup.title # <title>The Dormouse's story</title> print soup.head # <head><title>The Dormouse's story</title></head> print soup.a # <a class="sister" href="http://example.com/elsie" id="link1"><!-- Elsie --></a> print soup.p # <p class="title" name="dromouse"><b>The Dormouse's story</b></p> print type(soup.p) # <class 'bs4.element.Tag'>
我们可以利用 soup 加标签名轻松地获取这些标签的内容,这些对象的类型是bs4.element.Tag。
但是注意,它查找的是在所有内容中的第一个符合要求的标签。
对于Tag,它有两个重要的属性,分别是name和attrs。

print soup.name # [document] #soup 对象本身比较特殊,它的 name 即为 [document] print soup.head.name # head #对于其他内部标签,输出的值便为标签本身的名称 print soup.p.attrs # {'class': ['title'], 'name': 'dromouse'} # 在这里,我们把 p 标签的所有属性打印输出了出来,得到的类型是一个字典。 print soup.p['class'] # soup.p.get('class') # ['title'] #还可以利用get方法,传入属性的名称,二者是等价的 soup.p['class'] = "newClass" print soup.p # 可以对这些属性和内容等等进行修改 # <p class="newClass" name="dromouse"><b>The Dormouse's story</b></p>
NavigableString:
如果拿到标签后,还想获取标签中的内容。那么可以通过tag.string
获取标签中的文字。

print soup.p.string # The Dormouse's story print type(soup.p.string) # <class 'bs4.element.NavigableString'>thon
BeautifulSoup:
BeautifulSoup 对象表示的是一个文档的全部内容.大部分时候,可以把它当作 Tag 对象,它支持 遍历文档树 和 搜索文档树 中描述的大部分的方法。
因为 BeautifulSoup 对象并不是真正的HTML或XML的tag,所以它没有name和attribute属性。
但有时查看它的 .name 属性是很方便的,所以 BeautifulSoup 对象包含了一个值为 “[document]” 的特殊属性 .name。
Comment:
Tag , NavigableString , BeautifulSoup 几乎覆盖了html和xml中的所有内容,但是还有一些特殊对象.容易让人担心的内容是文档的注释部分:
Comment 对象是一个特殊类型的 NavigableString 对象

markup = "<b><!--Hey, buddy. Want to buy a used parser?--></b>" soup = BeautifulSoup(markup) comment = soup.b.string type(comment) # <class 'bs4.element.Comment'>
遍历文档树:
1. contents和children:

html_doc = """ <html><head><title>The Dormouse's story</title></head> <p class="title"><b>The Dormouse's story</b></p> <p class="story">Once upon a time there were three little sisters; and their names were <a href="http://example.com/elsie" class="sister" id="link1">Elsie</a>, <a href="http://example.com/lacie" class="sister" id="link2">Lacie</a> and <a href="http://example.com/tillie" class="sister" id="link3">Tillie</a>; and they lived at the bottom of a well.</p> <p class="story">...</p> """ from bs4 import BeautifulSoup soup = BeautifulSoup(html_doc,'lxml') head_tag = soup.head # 返回所有子节点的列表 print(head_tag.contents) # 返回所有子节点的迭代器 for child in head_tag.children: print(child)
2. strings 和 stripped_strings:
如果tag中包含多个字符串 [2] ,可以使用 .strings 来循环获取:

for string in soup.strings: print(repr(string)) # u"The Dormouse's story" # u'\n\n' # u"The Dormouse's story" # u'\n\n' # u'Once upon a time there were three little sisters; and their names were\n' # u'Elsie' # u',\n' # u'Lacie' # u' and\n' # u'Tillie' # u';\nand they lived at the bottom of a well.' # u'\n\n' # u'...' # u'\n'
输出的字符串中可能包含了很多空格或空行,使用 .stripped_strings 可以去除多余空白内容:

for string in soup.stripped_strings: print(repr(string)) # u"The Dormouse's story" # u"The Dormouse's story" # u'Once upon a time there were three little sisters; and their names were' # u'Elsie' # u',' # u'Lacie' # u'and' # u'Tillie' # u';\nand they lived at the bottom of a well.' # u'...'
搜索文档树:
1. find和find_all方法:
find
方法是找到第一个满足条件的标签后就立即返回,只返回一个元素。
find_all
方法是把所有满足条件的标签都选到,然后返回回去。
使用这两个方法,最常用的用法是出入name
以及attr
参数找出符合要求的标签。

soup.find_all("a",attrs={"id":"link2"}) 或者是直接传入属性的的名字作为关键字参数: soup.find_all("a",id='link2')
2. select方法:
使用以上方法可以方便的找出元素。但有时候使用css
选择器的方式可以更加的方便。使用css
选择器的语法,应该使用select
方法。以下列出几种常用的css
选择器方法:
(1)通过标签名查找:
print(soup.select('a'))
(2)通过类名查找:
通过类名,则应该在类的前面加一个.
。
print(soup.select('.sister'))
(3)通过id查找:
通过id查找,应该在id的名字前面加一个#号。
print(soup.select("#link1"))
(4)组合查找:
组合查找即和写 class 文件时,标签名与类名、id名进行的组合原理是一样的。

print(soup.select("p #link1"))
(5)通过属性查找:
查找时还可以加入属性元素,属性需要用中括号括起来,注意属性和标签属于同一节点,所以中间不能加空格,否则会无法匹配到。
print(soup.select('a[href="http://example.com/elsie"]'))
(6)获取内容:
以上的 select 方法返回的结果都是列表形式,可以遍历形式输出,然后用 get_text() 方法来获取它的内容。

soup = BeautifulSoup(html, 'lxml') print type(soup.select('title')) print soup.select('title')[0].get_text() for title in soup.select('title'): print title.get_text()
正则表达式和re模块:
正则表达式:
通俗理解:按照一定的规则,从某个字符串中匹配出想要的数据。这个规则就是正则表达式。
常用匹配规则:
匹配某个字符串:

text = 'hello' ret = re.match('he',text) print(ret.group()) >> he
点(.)匹配任意的字符:(点(.)不能匹配不到换行符)

text = "ab" ret = re.match('.',text) print(ret.group()) >> a 但是点(.)不能匹配不到换行符。示例代码如下: text = "ab" ret = re.match('.',text) print(ret.group()) >> AttributeError: 'NoneType' object has no attribute 'group'
\d匹配任意的数字:

text = "123" ret = re.match('\d',text) print(ret.group()) >> 1
\D匹配任意的非数字:(text是等于一个数字,那么就匹配不成功了)

text = "a" ret = re.match('\D',text) print(ret.group()) >> a 而如果text是等于一个数字,那么就匹配不成功了。示例代码如下: text = "1" ret = re.match('\D',text) print(ret.group()) >> AttributeError: 'NoneType' object has no attribute 'group'
\s匹配的是空白字符(包括:\n,\t,\r和空格):

text = "\t" ret = re.match('\s',text) print(ret.group()) >> 空白
\w匹配的是a-z
和A-Z
以及数字和下划线:(如果要匹配一个其他的字符,那么就匹配不到)

text = "_" ret = re.match('\w',text) print(ret.group()) >> _ 而如果要匹配一个其他的字符,那么就匹配不到。示例代码如下: text = "+" ret = re.match('\w',text) print(ret.group()) >> AttributeError: 'NoneType' object has no attribute
\W匹配的是和\w相反的:

text = "+" ret = re.match('\W',text) print(ret.group()) >> + 而如果你的text是一个下划线或者英文字符,那么就匹配不到了。示例代码如下: text = "_" ret = re.match('\W',text) print(ret.group()) >> AttributeError: 'NoneType' object has no attribute
[]组合的方式,只要满足中括号中的某一项都算匹配成功:

text = "0731-88888888" ret = re.match('[\d\-]+',text) print(ret.group()) >> 0731-88888888
之前讲到的几种匹配规则,其实可以使用中括号的形式来进行替代:
匹配多个字符:
*
:可以匹配0或者任意多个字符。
+
:可以匹配1个或者多个字符。最少一个。
?
:匹配的字符可以出现一次或者不出现(0或者1)。
{m}
:匹配m个字符。
{m,n}
:匹配m-n个字符。在这中间的字符都可以匹配到。
^(脱字号):表示以...开始:

text = "hello" ret = re.match('^h',text) print(ret.group()) 如果是在中括号中,那么代表的是取反操作.
$:表示以...结束:

# 匹配163.com的邮箱 text = "xxx@163.com" ret = re.search('\w+@163\.com$',text) print(ret.group()) >> xxx@163.com
|:匹配多个表达式或者字符串:

text = "hello|world" ret = re.search('hello',text) print(ret.group()) >> hello
贪婪模式和非贪婪模式:
贪婪模式:正则表达式会匹配尽量多的字符。默认是贪婪模式。
非贪婪模式:正则表达式会尽量少的匹配字符。

示例代码如下: text = "0123456" ret = re.match('\d+',text) print(ret.group()) # 因为默认采用贪婪模式,所以会输出0123456 >> 0123456 可以改成非贪婪模式,那么就只会匹配到0。示例代码如下: text = "0123456" ret = re.match('\d+?',text) print(ret.group())
转义字符和原生字符串:
转移字符:
在正则表达式中,有些字符是有特殊意义的字符。因此如果想要匹配这些字符,那么就必须使用反斜杠进行转义。
原生字符串:
在正则表达式中,\
是专门用来做转义的。在Python中\
也是用来做转义的。因此如果想要在普通的字符串中匹配出\
,那么要给出四个\
。

text = "apple \c" ret = re.search('\\\\c',text) print(ret.group()) 因此要使用原生字符串就可以解决这个问题: text = "apple \c" ret = re.search(r'\\c',text) print(ret.group())
练习:

验证手机号码:手机号码的规则是以1开头,第二位可以是34587,后面那9位就可以随意了。示例代码如下: text = "18570631587" ret = re.match('1[34587]\d{9}',text) print(ret.group()) >> 18570631587 而如果是个不满足条件的手机号码。那么就匹配不到了。示例代码如下: text = "1857063158" ret = re.match('1[34587]\d{9}',text) print(ret.group()) >> AttributeError: 'NoneType' object has no attribute 验证邮箱:邮箱的规则是邮箱名称是用数字、数字、下划线组成的,然后是@符号,后面就是域名了。示例代码如下: text = "hynever@163.com" ret = re.match('\w+@\w+\.[a-zA-Z\.]+',text) print(ret.group()) 验证URL:URL的规则是前面是http或者https或者是ftp然后再加上一个冒号,再加上一个斜杠,再后面就是可以出现任意非空白字符了。示例代码如下: text = "http://www.baidu.com/" ret = re.match('(http|https|ftp)://[^\s]+',text) print(ret.group()) 验证身份证:身份证的规则是,总共有18位,前面17位都是数字,后面一位可以是数字,也可以是小写的x,也可以是大写的X。示例代码如下: text = "3113111890812323X" ret = re.match('\d{17}[\dxX]',text) print(ret.group()) 匹配0-100之间的数字: text = '99' ret = re.match('[1-9]?\d$|100$',text) print(ret.group()) >> 99 而如果text=101,那么就会抛出一个异常。示例代码如下: text = '101' ret = re.match('[1-9]?\d$|100$',text) print(ret.group()) >> AttributeError: 'NoneType' object has no attribute 'group'
re模块中常用函数:
match:
从开始的位置进行匹配。如果开始的位置没有匹配到。就直接失败了。

text = 'hello' ret = re.match('h',text) print(ret.group()) >> h 如果第一个字母不是h,那么就会失败。示例代码如下: text = 'ahello' ret = re.match('h',text) print(ret.group()) >> AttributeError: 'NoneType' object has no attribute 'group' 如果想要匹配换行的数据,那么就要传入一个flag=re.DOTALL,就可以匹配换行符了。示例代码如下: text = "abc\nabc" ret = re.match('abc.*abc',text,re.DOTALL) print(ret.group())
search:
在字符串中找满足条件的字符。如果找到,就返回。说白了,就是只会找到第一个满足条件的。

text = 'apple price $99 orange price $88' ret = re.search('\d+',text) print(ret.group()) >> 99
分组:
在正则表达式中,可以对过滤到的字符串进行分组。分组使用圆括号的方式。
1.group
:和group(0)
是等价的,返回的是整个满足条件的字符串。
2.groups
:返回的是里面的子组。索引从1开始。
3.group(1)
:返回的是第一个子组,可以传入多个。

text = "apple price is $99,orange price is $10" ret = re.search(r".*(\$\d+).*(\$\d+)",text) print(ret.group()) print(ret.group(0)) print(ret.group(1)) print(ret.group(2)) print(ret.groups())
findall:
找出所有满足条件的,返回的是一个列表。
sub:
用来替换字符串。将匹配到的字符串替换为其他字符串。
split:
使用正则表达式来分割字符串。

text = "hello world ni hao" ret = re.split('\W',text) print(ret) >> ["hello","world","ni","hao"]
compile:
对于一些经常要用到的正则表达式,可以使用compile
进行编译,后期再使用的时候可以直接拿过来用,执行效率会更快。
而且compile
还可以指定flag=re.VERBOSE
,在写正则表达式的时候可以做好注释。

text = "the number is 20.50" r = re.compile(r""" \d+ # 小数点前面的数字 \.? # 小数点 \d* # 小数点后面的数字 """,re.VERBOSE) ret = re.search(r,text) print(ret.group())