xpath(XML Path Language)
是一门在XML
和HTML
文档中查找信息的语言,可用来在XML
和HTML
文档中对元素和属性进行遍历。
Chrome
插件XPath Helper
。Firefox
插件XPath Checker
。选取节点:
XPath使用路径表达式来选取XML文档中的节点或者节点集。这些路径表达式和我们在常规的电脑文件系统中看到的表达式非常相似。
表达式 | 描述 | 示例 | 结果 |
---|---|---|---|
nodename | 选取此节点的所有子节点 | bookstore | 选取bookstore下所有的子节点 |
/ | 如果是在最前面,代表从根节点选取。否则选择某节点下的某个节点 | /bookstore | 选取根元素下所有的bookstore节点 |
// | 从全局节点中选取节点,随便在哪个位置 | //book | 从全局节点中找到所有的book节点 |
@ | 选取某个节点的属性 | //book[@price] | 选取所有book节点的price属性 |
谓语:
谓语用来查找某个特定节点或者包含某个指定的值的节点,被嵌套在方括号中。
在下面的表格中,我们列出了带有谓语的一些路径表达式,以及表达式的结果:
路径表达式 | 描述 |
---|---|
/bookstore/book[1] | 选取bookstore下的第一个子元素(下标是从1开始的。) |
/bookstore/book[last()] | 选取bookstore下的倒数第二个book元素 |
/bookstore/book[position()❤️] | 选取bookstore下前面两个子元素 |
//book[@price] | 选取拥有price属性的book元素 |
//book[@price=10] | 选取所有属性price等于10的book元素 |
//book[contains(@class,‘fl’)] | 当节点拥有多个class时,可使用模糊匹配。 |
通配符:
*
表示通配符。
通配符 | 描述 | 示例 | 结果 |
---|---|---|---|
* |
匹配任意节点 | /bookstore/* | 选取bookstore下所有的 |
@* | 匹配节点中的任何属性 | //book[@*] | 选取所有带有属性的book元素 |
选取多个路径:
通过在路径表达式中使用|
运算符,可以选取若干个路径。
示例如下:
# 选取所有book元素以及book元素下所有的title元素
//bookstore/book | //book/title
运算符:
运算符 | 描述 | 实例 | 返回值 |
---|---|---|---|
| | 计算两个节点集 | //book | //cd | 返回所有拥有book和cd元素的节点元素 |
+ | 加法 | 6+4 | 10 |
- | 减法 | 6-4 | 2 |
* | 乘法 | 6*4 | 24 |
div | 除法 | 8 div 4 | 2 |
= | 等于 | price=9.80 | 如果price=9.80,则返回true。否则返回false。 |
!= | 不等于 | price!=9.80 | 如果price不等于9.80,则返回true。否则返回false。 |
< | 小于 | price<9.80 | 如果真,则返回true,否则返回false |
<= | 小于或等于 | price<=9.80 | 如果真,则返回true,否则返回false |
> | 大于 | price>9.80 | 如果真,则返回true,否则返回false |
>= | 大于或等于 | price>=9.80 | 如果真,则返回true,否则返回false |
or | 或 | price=9.80 or price=9.70 | 如果真,则返回true,否则返回false |
and | 与 | price=9.80 and price=9.70 | 如果真,则返回true,否则返回false |
mod | 计算除法的余数 | 5 mod 2 | 1 |
lxml是一个HTML/XML的解析器,主要的功能是如何解析和提取HTML/XML数据。
lxml和正则一样,也是用C实现的,是一款高性能的Python HTML/XML解析器,我们可以利用之前学习的XPath语法,来快速定位特定元素以及节点信息。
lxml python官方文档:lxml - Processing XML and HTML with Python
需要安装C语言库,可使用pip install lxml。
1.04、lxml的基本使用:
我们可以利用他来解析HTML代码,并且在解析HTML代码的时候,如果HTML代码不规范,他会自动的进行补全。示例代码如下:
从字符串中读取HTML代码:
# 使用 lxml的etree库
from lxml import etree
text = '''
你好
'''
html = etree.HTML(text)
print(etree.tostring(html,encoding='utf-8').decode('utf-8'))
从文件中读取HTML代码:
假设存在一个hello.html文件。利用etree.parse
方法,这个方法默认的解析器是XML解析,不会像etree.HTML
类一样进行代码补充。示例代码如下:
from lxml import etree
# 有的网站的代码并不规范,直接parse解析会失败,这时需要修改解析器
parser = etree.HTMLParser(encoding='utf-8')
html = etree.parse('hello.html',parser=parser)
print(etree.tostring(html,encoding='utf-8').decode('utf-8'))
print(result)
注意: etree.parse
默认的是XML的解析器,有的不规则的网页会解析失败,这时修改parse
的解析器。parser = etree.HTMLParser(encoding='utf-8')
,然后把parser
传入etree.parse()
即可。
1.05、在lxml中使用XPath语法:
1.获取所有li标签:
from lxml import etree
html = etree.parse('hello.html')
result = html.xpath('//li')
# 打印标签的元素集合
print(result)
2.获取所有li元素下的所有class属性的值:
from lxml import etree
html = etree.parse('hello.html')
result = html.xpath('//li/@class')
print(result)
3.获取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)
4.获取li标签下href为www.baidu.com
的a标签的文本信息,使用text()
获取文本信息:
from lxml import etree
html = etree.parse('hello.html')
result = html.xpath('//li/a[@href="www.baidu.com"]/text()')
print(result)
注意事项:
1.使用xpath
语法,应该使用Element.xpath
方法。来执行xpath的选择。
result = html.xpath('//li')
xpath
返回的永远是一个列表。
2.获取文本,是通过xpath
中的text()
函数。示例代码如下:
html.xpath('//li/a[1]/text()')
3.在某个标签下,再执行xpath
函数,获取这个标 签下的子孙元素,那么应该在斜杠之前加一个.
,代表是在当前元素下获取。
address = tr.xpath('./td[4]/text()')
2、BeautifulSoup4
库
和lxml一样,BeautifulSoup也是一个HTML/XML
的解析器,主要的功能也是如何解析和提取HTML/XML
数据。
lxml
指挥局部遍历,而BeautifulSoup
是基于HTML DOM
的,会载入整个文档,解析整个DOM
树,因此时间和内存开销都会大很多,所以性能低于lxml
。
BeautifulSoup
用来解析HTML
比较简单,api
非常人性化,支持CSS
选择器、python
标准库中的HTML
解析器,也支持lxml
的XML
解析器。
2.01、安装和文档:
- 安装:
pip install bs4
- 中文文档:Beautiful Soup 4.4.0 文档 — Beautiful Soup 4.2.0 中文 文档
2.02、几大解析工具对比:
解析工具
解析速度
使用难度
BeautifulSoup
最慢
最简单
lxml
快
简单
正则
最快
最难
2.03、四个常用的对象:
Beautiful Soup
将复杂HTML
文档转换成一个复杂的树形结构,每个节点都是python
对象,所有对象都可以归纳为4种:
Tag
:BeautifulSou
中所有的标签都是Tag
类型,并且BeautifulSou
的对象其实本质上也是一个Tag
类型。所以其实一些方法比如:find
、find_all
并不是BeautifulSou
的,而是Tag
的。
NavigatableString
:继承自python
中的str
,用起来和str一样。
BeautifulSouP
:继承自Tag
。用来生成BeautifulSou
树的。对于一些查找方法,比如:find
、select
这些,其实还是Tag
的。
Comment
:继承自NavigableString
。
2.03-1、Tag
通俗点讲就是HTML
中的一个个的标签
soup = BeautifulSoup(html,'lxml')
table = soup.find('table')
print(type(table))
我们可以利用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) #还可以对这些属性和内容进行修改
2.03-2、NavigableString:
如果拿到标签后,还想获取标签中的内容。那么可以通过tag.string
获取标签中的文字。
print(soup.p.string)
# The Document's story
print(type(soup.p.string))
# thon
2.03-3、BeautifulSoup:
BeautifulSoup
对象表示的是一个文档的全部内容,大部分时候,可以把它当作Tag
对象,它支持遍历文档树和搜索文档树中描述的大部分方法。
因为BeautifulSoup
对象并不是真正的HTML
和XML
的Tag
,所以它没有name
和attribute
属性,但有时查看它的name
属性是很方便的。所以BeautifulSoup
对象包含了一个值为'[document]'
的特殊属性.name
。
soup.name
# '[document]'
2.03-4、Comment:
Tag
、NavigableString
、BeautifulSoup
几乎覆盖了html
和xml
中所有的内容,但是还有一些特殊对象,容易让人担心内容是文档的注释部分:
markup = ""
soup = BeautifulSoup(markup)
comment = soup.b.string
print(type(comment))
#
Comment
对象是一个特殊类型的NavigableString
对象。
2.04、搜索文档树
2.04-1、find和find_all方法:
搜索文档书,一般用的比较多的方法就是两个方法,一个是find,一个是find_all。find方法是找到第一个满足条件的标签后立即返回,只返回一个元素。find_all方法是把所以满足条件的表签都返回。
实例演示:
1.获取所有tr标签
trs = soup.find_all('tr')
for tr in trs:
print(tr)
2.获取第二个tr标签
tr = soup.find_all('tr',limit=2)[1]
3.获取所有class等于even的tr标签
trs = soup.find_all('tr',attrs={'class':'even'})
for tr in trs:
print(tr)
4.将所有id等于test,class也等于test的a标签提取出来。
aList = soup.find_all('a',id='test',class='test')
# 也可以使用attrs属性选择
for a in aList:
print(a)
5.获取所有a标签的href属性
aList = soup.find_all('a')
for a in aList:
# 1.通过下标操作的方式
print(a['href'])
# 2.通过attrs属性的方式
print(a.attrs['href'])
6.获取所有职位信息(纯文本)
- 1.string提取单个文本信息,
- 2.strings提取标签下所有文本信息,
- 3.stripped_strings提取标签下所有非空的文本信息。
trs = soup.find_all('tr')[1:]
zhiwei = {}
for tr in trs:
# tds = tr.find_all('td')
# title = tds[0].string
# city = tds[1].string
# zhiwei['title'] = title
# zhiwei['city'] = city
infos = list(tr.stripped_strings)
zhiwei['title'] = infos[0]
zhiwei['city'] = infos[1]
print(zhiwei)
2.04-2、select方法:
使用以上方法可以方便的找出元素,但有时候使用css选择器的方法可以更加方便。使用css选择器的语法,应该使用select方法。一下列出几种常用的css选择器方法:
1.通过标签名查找:
print(soup.select('a'))
2.通过类名查找:
通过类名查找,则应该在类名前面加一个.
。比如查找class=sister
的标签。
tg = soup.select('.sister')
3.通过id查找:
通过id查找,一个在id名字前面加#
号。
print(soup.select('#idname'))
4.组合查找:
组合查找即和写class文件时,标签名与类名、id名进行组合原理一样,例如查找p标签中,id等于link1的内容,二者需要用空格分开:
print(soup.select('p #link1'))
直接子标签查找,这使用>
分隔:
print(soup.select('head>title'))
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())
2.04-3、find_all的使用:
- 在提取多个标签的时候,第一个参数是标签的名字。然后图个在提取标签的时候想要使用标签属性进行过滤,你们可以在这个方法中通过关键字参数的形式,将属性名以及对应的值传进去。或者是使用
attrs
属性,将所有的属性以及对于的值房子啊一个字典中传给attrs
属性。
- 有时候,在提取标签的时候,不想提取那么多,那么可以使用
limit
参数。进行限制。
2.04-4、find和find_all的区别:
- find:只返回满足条件的第一个标签
- find_all:返回满足条件的所有标签。是个列表。
2.04-5、find和find_all的过滤条件:
- 关键字参数:将属性的名字作为关键字参数的名字,以及属性的值作为关键字参数的值进行过滤。
- attrs参数:将属性条件放到一个字典中,传给attrs参数。
2.04-6、获取标签的属性:
# 1.通过下标操作的方式
print(a['href'])
# 2.通过attrs属性的方式
print(a.attrs['href'])
2.04-7、string、strings、stripped_strings属性和get_text方法:
- string:获取某个标签下的非标签字符串。返回的是个字符串。如果这个标签下有多行字符,那么就获取不到了。
- strings:获取某个标签下的子孙非标签字符串。返回的是个生成器
- stripped_strings:获取某个标签下的子孙非标签字符串,去掉空白字符。返回的是个生成器。
- get_text:获取某个标签下的子孙非标签字符串,不是以列表的形式返回。只是普通字符串。
2.05、遍历文档树
2.05-1、contents和children:
返回某个标签下的直接子元素。其中也包括字符串。他们的区别是:contents
返回的是一个列表,children
返回的是一个迭代器。
soup = BeautifulSoup(html,'lxml')
head_tag = soup.head
# 返回所有的子节点的列表
print(head_tag.contents)
# 返回所有的子节点的迭代器
for child in head_tag.children:
print(child)
2.05-2、strings和stripped_strings
如果tag
中包含多个字符串,可以使用.strings
来循环获取:
for string in soup.strings:
print(string)
3、正则表达式和re模块
什么是正则表达式(通俗理解):按照一定的规则,从某个字符串中匹配出想要的数据。这个规则就是正则表达式。
###3.01、正则表达式符号:
1.[]
:
- 1.用来指定字符集:[asdfghjkl],[a-zA-Z]
- 2.元字符在字符集中不起作用:[akn$]
- 3.不急匹配不在区间范围内的字符:[ ^io],匹配除io之外的任意字符。
2.^
:
- 1.匹配行首。除非设置MULTILINE标志,它只是匹配字符串的开始。在MULTILINE模式里,它也可以直接匹配字符串中的每个换行。
3.$
:
- 1.匹配行尾,行尾被定义为要么是字符串尾,要么是一个换行字符后面的任何位置。
4.\
:
-
1.反斜杠后面可以加不同的字符以表示不同的特殊意义。
-
也可以用于取消所有的元字符,即当转义字符。
符号
含义
.
匹配除’\n’之外的任何单个字符,如果要匹配包括’\n’在内的任何字符,使用‘[.\n]’
\d
匹配任何十进制数,相当于类[0-9]
\D
匹配任何非数字字符;相当于类[ ^0-9]
\s
匹配任何空白字符,相当于类[\t\n\r\f\v]
\S
匹配任何非空白字符,相当于类[ ^\t\n\r\f\v]
\w
匹配任何字母数字字符,相当于类[a-zA-Z0-9_]
\W
匹配任何非字母数字字符,相当于类[ ^a-zA-Z0-9_]
5. 重复:
- 1.正则表达式第一个功能是能够匹配不定长的字符集,另一个功能就是可以指定正则表达式的一部分的重复次数。在{ }捏填写重复次数
6.*
:
- 1.指定前一个字符可以别匹配零次或更多次,而不是只有一次。匹配引擎会试着重复尽可能多的次数(不超过整数界定范围,20亿)
- 2.a[bcd]*b----‘abcbd’。
7.+
:
- 1.表示匹配一次或更多次。
- 2.和*的区别,*匹配零次或更多次,所有可以根本就不出现,而’+’则要求至少出现一次
8.?
:
- 1.加在重复的后面,做最小匹配,即非贪婪模式。
- 2.匹配一次或零次,你可以认为它用于标识某事物是可选的
9.{m,n}
:
- 1.其中m和n是十进制整数,该限定符的意思是至少有m个重复,至多到n个重复。
- 2.忽略m会认为下边界是0,而忽略n的结果将是上边界无穷大(实际是20亿)
- 3.{0,}等同于‘’,{1,}等同于‘+’,而{0,1}则与‘?’相同。如果可以的话,最好使用‘’,‘+’或‘?’。
10.()
分组:
- 1.利用
()
来进行多种正则的选择。匹配时优先返回分组的值。
11.|
:
- 1.匹配多个表达式或字符串。
3.02、正则表达式常用的匹配规则:
1.匹配某个字符串:
text = 'hello'
ret = re.march('he',text)
print(ret.group())
>>he
以上便可以在hello
中,匹配出he
。
3.03、转义字符和原生字符串
在正则表达式中,有些字符串是有特殊意义的字符。因此如果想要匹配这些字符,那么就必须使用反斜杠进行转义。比如$
代表的是以…结尾,如果想要匹配$
,那么就必须使用\$
。
text = 'apple price is \$99,orange is $88'
ret = re.search('\$(\d+)',text)
print(ret.group())
>>$99
原生字符串:
在正则表达式中,\
是专门用来转义的。在python
中\
也是用来转义的。因此如果想要在普通字符串中匹配出\
,那么要给出4个\
。
text = 'apple \c'
ret = re.search('\\\\c',text)
print(ret.group())
因此要使用原生字符串就可以解决这个问题:
text = 'apple \c'
ret = re.search(r'\\c',text)
print(ret.group())
3.04、正则表达式函数:
3.04-1、re.compile()
函数
compile函数用于编译正则表达式,生成一个正则表达式( Pattern )对象,供 match() 和 search() 这两个函数使用。
语法格式为:re.compile(pattern[, flags])
-
pattern:一个字符串形式的正则,字符串前加r
,反斜杠就不会被当作转义字符。
-
flags:可选,表示匹配模式,比如忽略大小写,多行模式等,具体参数如下:
符号
含义
re.I
忽略大小写
re.L
表示特殊字符集\w, \W, \b, \B, \s, \S 依赖于当前环境
re.M
多行模式
re.S
即为.
并且包括换行符在内的任意字符(单纯的.
不包括换行符)
re.U
表示特殊字符集 \w, \W, \b, \B, \d, \D, \s, \S 依赖于 Unicode 字符属性数据库
re.X
为了增加可读性,忽略空格和 # 后面的注释
3.04-2、用于匹配的函数:
方法/属性
作用
match()
在字符串刚开始的位置匹配,如果想要匹配换行符,传入一个flags=re.DOTALL
就可以了。
search()
扫描字符串,找到这个RE匹配的位置
findall()
找到RE匹配的所有子串,并把它们作为一个列表返回
finditer()
找到RE匹配的所有子串,并把它们作为一个迭代器返回
如果没有匹配到,match()和search()将返回None,如果成功,返回一个‘MatchObject’实例 。
MatchObject实例方法(即匹配成功后使用的方法):
方法/属性
作用
group()
返回被RE匹配的字符串。
start()
返回匹配开始的位置。
end()
返回匹配结束的位置
span()
返回一个元组包含匹配(开始,结束)
3.04-3、分组:
在正则表达式中,可以对过滤到的字符串进行分组。分组使用()
。
group
:和group(0)
是等价的,返回的是整个满足条件的字符串。
groups
:返回的是里面的子组。索引从1开始。
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())
3.04-4、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())
3.04-5、search():
在字符串中找满足条件的字符。如果找到了,就返回。只会返回第一个满足条件的。
text = 'apple price is $99,orange price is $88'
ret = re.search('\d+',text)
print(ret.group())
>>99
3.04-6、findall():
找出满足条件的,返回的是一个列表。
text = 'apple price is $99,orange price is $88'
ret = re.search('\d+',text)
print(ret.group())
>>['99','88']
3.04-7、检索替换re.sub():
re模块提供了re.sub()用于替换字符串中的匹配项。
语法:re.sub(pattern,repl,string,count=0,flags=0)
参数:
- pattern:正则中的模式字符串。
- repl:替换的字符串,也可为一个函数。
- string:要被查找替换的原始字符串。
- count:模式匹配后替换的最大次数,默认为0,表示替换所有的匹配。
3.04-8、检索替换re.subn()
返回结果比re.sub()多了一个替换次数。
3.04-9、re.split()
split 方法按照能够匹配的子串将字符串分割后返回列表,它的使用形式如下:
re.split(pattern, string[, maxsplit=0, flags=0])
-
pattern:匹配的正则表达式
-
string:要匹配的字符串
-
maxsplit:分割次数,maxsplit=1分割一次,默认为0,不限次数。
import re
s = '123+456-789*111'
re.split(r'[\+\-\*]',s)
>['123','456','789','111']