目录
对于网页的节点来说,可以定义id、class或其他属性,而且节点之间还有层次关系,在网页中可以通过XPath或CSS选择器来定位一个或多个节点。相关的解析库也比较多,包括lxml、Beautiful Soup、pyquery、parsel等。
1 XPath的使用
XPath的全程是XML Path Language,即XML路径语言,用来在XML文档中查找信息。虽然最初是用来搜寻XML文档的,但同样适用于HTML文档的搜索。
1.1 XPath概览
包含许多定位的节点。
1.2 XPath常用规则
XPath的一个常用匹配规则://title[@lang='eng'],代表选择所有名称为title,同时属性为lang的值为eng的节点。
1.3 准备工作
使用lxml库,利用XPath对HTML进行解析。
pip install lxml
1.4 实例引入
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>
</ul>
</div>
'''
html=etree.HTML(text)
result=etree.tostring(html)
print(result.decode('utf-8'))
<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>
from lxml import etree
html=etree.parse('./test.html',etree.HTMLParser())
result=etree.tostring(html)
print(result.decode('utf-8'))
1.5 所有节点
一般会用以//开头的XPath规则,来选取所有符合要求的节点。
from lxml import etree
html=etree.parse('./test.html',etree.HTMLParser())
result=html.xpath('//*')
print(result)
*代表匹配的所有节点,也就是获取整个HTML文本中的所有节点。从运行结果可以看到,返回形式是一个列表,其中每个元素是element类型,类型后面跟着节点的名称。
如果想要获取所有的li节点,实例如下:
from lxml import etree
html=etree.parse('./test.html',etree.HTMLParser())
result=html.xpath('//li')
print(result)
print(result[0])
从运行结果也可以看出,提取结果是一个列表,其中每个元素都是element类型,如果想要取出其中一个对象,可以直接用中括号加索引获取,如[0]。
1.6 节点
通过/或//即可查找元素的子节点或子孙节点。如果想选择li节点的所有直接子节点a,则可以通过以下代码实现:
from lxml import etree
html=etree.parse('./test.html',etree.HTMLParser())
result=html.xpath('//li//a')
print(result)
如果要获取节点的所有子孙节点,可以使用//。
from lxml import etree
html=etree.parse('./test.html',etree.HTMLParser())
result=html.xpath('//ul//a')
print(result)
但是如果这里用//ul/a,就无法获取任何结果了,因为/用于获取直接子节点,但ul节点下没有直接的a子节点,只有li节点。
1.7 父节点
通过连续的/或//可以查找子节点或子孙节点,如果指导子节点,查找父节点,可以通过..实现。
例如,首先选中href属性为link4.html的a节点,然后获取其父节点,在获取父节点的class属性,代码如下:
from lxml import etree
html=etree.parse('./test.html',etree.HTMLParser())
result=html.xpath('//a[@href='link4.html]'/../@class')
print(result)
运行结果如下:
['item-1']
此外,也可以通过parent::获取父节点,代码如下:
from lxml import etree
html=etree.parse('./test.html',etree.HTMLParser())
result=html.xpath('//a[@href='link4.html]'/parent::*/@class')
print(result)
1.8 属性匹配
在选取节点的时候,还可以使用@符号实现属性过滤。例如,要选取class属性为item-0的li节点,可以通过以下代码实现:
from lxml import etree
html=etree.parse('./test.html',etree.HTMLParser())
result=html.xpath('//li[@class='item-0']')
print(result)
1.9 文本获取
用XPath中的text方法可以获取节点中的文本,接下来尝试获取前面li节点中的文本,代码如下:
from lxml import etree
html=etree.parse('./test.html',etree.HTMLParser())
result=html.xpath('//li[@class='item-0']/text()')
print(result)
from lxml import etree
html=etree.parse('./test.html',etree.HTMLParser())
result=html.xpath('//li[@class='item-0']/a/text()')
print(result)
如果使用//,能够获取到的结果,代码:
from lxml import etree
html=etree.parse('./test.html',etree.HTMLParser())
result=html.xpath('//li[@class='item-0']//text()')
print(result)
1.10 属性获取
利用@符号,可以获取所有li节点下所有a节点的href属性:
from lxml import etree
html = etree.parse('./test.html',etree.HTMLParser())
result = html.xpath('//li/a/@href')
print(result)
通过@href获取节点的href属性。此处与属性匹配方法不同,属性匹配是用中括号加属性名和值来限定某个属性,如[@href="link1.html"],此处的@href是指获取节点的某个属性。
1.11 属性多值匹配
有时候,某些节点的某个属性可能有多个值,例如:
from lxml import etree
text='''
<li class='li li-first'><a href='link.html'>first item</a></li>
'''
html=etree.HTML(text)
result=html.xpath('//li[@class='li']/a/text()')
print(result)
这里的HTML文本中li节点的class属性就有两个值:li和li-frist,需要用到contains方法,代码如下:
from lxml import etree
text='''
<li class='li li-first'><a href='link.html'>first item</a></li>
'''
html=etree.HTML(text)
result=html.xpath('//li[contains(@class,'li')]/a/text()')
print(result)
上面的contains,给第一个参数传入属性名称,第二个参数传入属性值,只要传入的属性包含传入的属性值,就可以完成匹配。
1.12 多属性匹配
根据多个属性确定一个节点,这时需要同时匹配多个属性,运算符and用于连接多个属性,代码如下:
from lxml import etree
text='''
<li class='li li-first'><a href='link.html'>first item</a></li>
'''
html=etree.HTML(text)
result=html.xpath('//li[contains(@class='li') and @name='item']/a/text()')
print(result)
此外,还有许多运算符:
1.13 按序选择
在选择节点时,某些属性可能同时匹配了多个节点,但我么只想要其中的某一个,可以使用往中括号中传入索引的方法获取特定次序的节点,代码:
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>
</ul>
</div>
'''
html=etree.HTML(text)
result=html.xpath('//li[1]/a/text()')
print(result)
result=html.xpath('//li[last()]/a/text()')
print(result)
result=html.xpath('//li[position()<3]/a/text()')
print(result)
result=html.xpath('//li[last()-2]/a/text()')
print(result)
注:这里的代码序号以1开头,而不是0。last()表示最后,position表示位置。
1.14 节点轴选择
XPath提供了许多节点轴的选择方法,包括获取子元素、兄弟元素、父元素、祖先元素等,代码:
from lxml import etree
text ='''
<div>
<ul>
<li class="item-0"><a href="link1.html"><span>first item</span></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>
</ul>
</div>
'''
html = etree.HTML(text)
result = html.xpath('//li[1]/ancestor::*')
print(result)
result = html.xpath('//li[1]/ancestor::div')
print(result)
result = html.xpath('//li[1]/attribute::*')
print(result)
result = html.xpath('//li[1]/child::a[@href="link1.html"]')
print(result)
result = html.xpath('//li[1]/descendant::span')
print(result)
result = html.xpath('//li[1]/following::*[2]')
print(result)
result = html.xpath('//li[1]/following-sibling::*')
print(result)
第一次选择,调用ancestor轴,可以获取所有祖先节点,其后需要跟两个冒号,然后是节点的选择器(*表示匹配所有节点)。返回的是第一个li节点的所有祖先节点,包括html、body、div和ul。
第二次选择,加了限定条件。
第三次选择,调用了attribute轴,可以获取所有属性值,其后跟的选择器还是*,代表获取节点的所有属性,返回值就是li节点的所有属性值。
第四次选择,调用了child轴,可以获取所有直接子节点,这里的限定条件指的事选取href属性为link.html的a节点。
第五次选择,调用了descendant轴,可以获取所有子孙节点,这里的限定条件指获取span节点。
第六次选择,调用了following轴,获取当前节点之后的所有节点,这里的限定条件指获取了第二个后续节点。
第七次选择,调用了following-sibling轴,获取当前节点之后的所有同级节点。
2 Beautiful Soup
2.1 简介
python的一个html或xml的解析库,用它可以方便地从网页中提取数据。
2.2 解析器
通过上表,lxml解析器具有解析html和xml的功能。
使用lxml解析器,只需要在初始化时,把第二个参数修改为lxml即可:
from bs4 import BeautifulSoup
soup=BeautifulSoup('<p>Hello</p>','lxml')
print(soup.p.string)
2.3 准备工作
pip3 install beautifulsoup4
2.4 基本使用
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/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,'lxml')
print(soup.prettify())
print(soup.title.string)
2.5 节点选择器
直接调用节点的名称即可选择节点,然后调用string属性就可以得到节点内的文本了。
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/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,'lxml')
print(soup.title)
print(type(soup.title))
print(soup.title.string)
print(soup.head)
print(soup.p)
2.6 提取信息
2.6.1 获取名称
利用name属性可以获取节点的名称。
print(soup.title.name)
2.6.2 获取属性
一个节点可能有多个属性,例如id和class,选择某个节点元素后,可以调用attrs获取其所有属性:
print(soup.p.attrs)
print(soup.p.attrs['name'])
print(soup.p['name'])
print(soup.p['Class'])
2.6.3 获取内容
print(soup.p.string)
2.6.4 嵌套选择
html ='''
<html><head><title>The Dormouse's story</title></head>
<body>
'''
from bs4 import BeautifulSoup
soup=BeautifulSoup(html,'lxml')
print(soup.head.title)
print(type(soup.head.title))
print(soup.head.title.string)
2.7 关联选择
在做选择的过程中,需要先选中某一个节点,然后再以这个节点为基准选子节点、父节点、兄弟节点等。
2.7.1 子节点和子孙节点
选取节点之后,如果想要获取它的直接子节点,可以调用contens属性,代码:
<html>
<head>
<title>The Dormouse's story</title>
</head>
<body>
<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">
<span>Elsie</span>
</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,'lxml')
print(soup.p.contents)
可以看到,返回结果是列表形式:p节点里既包括文本,又包括节点。列表中的每个元素都是p节点的直接子节点。像第一个a节点里面包括的span节点,相当于孙子节点,但是返回结果并没有把span节点单独选出来。所以说,contens属性得到的结果是直接子节点组成的列表。
同样,可以调用children属性得到相应的结果:
from bs4 import BeautifulSoup
soup = BeautifulSoup(html, 'lxml')
print(soup.p.children)
for i, child in enumerate(soup.p.children):
print(i, child)
这里掉用children属性来选择,返回结果是生成器类型,利用for循环输出了相应内容。
如果要得到所有的子孙节点,则可以调用descendants属性:
from bs4 import BeautifulSoup
soup = BeautifulSoup(html, 'lxml')
print(soup.p.descendants)
for i, child in enumerate(soup.p.descendants):
print(i, child)
2.7.2 父节点和祖先节点
如果要获取某个节点元素的父节点,可以掉用parents属性:
html = """
<html>
<head>
<title>The Dormouse's story</title>
</head>
<body>
<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">
<span>Elsie</span>
</a>
</p>
<p class="story">...</p>
"""
from bs4 import BeautifulSoup
soup = BeautifulSoup(html, 'lxml')
print(soup.a.parent)
这里选的是第一个a节点的父节点元素,a节点的父节点是p节点,所以输出结果是p节点及其内部内容。
如果想要获取所有祖先节点,可以调用parents属性:
html = """
<html>
<body>
<p class="story">
<a href="http://example.com/elsie" class="sister" id="link1">
<span>Elsie</span>
</a>
</p>
"""
from bs4 import BeautifulSoup
soup = BeautifulSoup(html, 'lxml')
print(type(soup.a.parents))
print(list(enumerate(soup.a.parents)))
2.7.3 兄弟节点
如果想要获取同级节点,也就是兄弟节点:
html = """
<html>
<body>
<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">
<span>Elsie</span>
</a>
Hello
<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>
"""
from bs4 import BeautifulSoup
soup = BeautifulSoup(html, 'lxml')
print('Next Sibling', soup.a.next_sibling)
print('Prev Sibling', soup.a.previous_sibling)
print('Next Siblings', list(enumerate(soup.a.next_siblings)))
print('Prev Siblings', list(enumerate(soup.a.previous_siblings)))
可以看到,这里调用了4个属性。next_sibling和previous_sibling分别用于获取节点的下一个和上一个兄弟节点,next_siblings和previous_siblings则分别返回后面和前面的所有兄弟节点。
2.7.4 提取信息
关联元素的信息获取,代码如下:
html = """
<html>
<body>
<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">Bob</a><a href="http://example.com/lacie" class="sister" id="link2">Lacie</a>
</p>
"""
from bs4 import BeautifulSoup
soup = BeautifulSoup(html, 'lxml')
print('Next Sibling:')
print(type(soup.a.next_sibling))
print(soup.a.next_sibling)
print(soup.a.next_sibling.string)
print('Parent:')
print(type(soup.a.parents))
print(list(soup.a.parents)[0])
print(list(soup.a.parents)[0].attrs['class'])
如果返回结果是单个节点,可以直接掉用string、attrs等属性获取其文本和属性;如果返回结果是包含多个节点的生成器,则可以现将结果转为列表,再从中取出某个元素,之后调用string、attrs等属性即可获取对应节点的文本和属性。
2.8 方法选择器
利用以下方法可以灵活查询相应参数:
2.8.1 find_all
查询所有符合条件的元素,可以给它传入一些属性或文本得到符合条件的元素,API如下:
find_all(name,attrs,recursive,text,**kwargs)
2.8.2 name
根据name参数查询元素,代码如下:
html = '''
<div class="panel">
<div class="panel-heading">
<h4>Hello</h4>
</div>
<div class="panel-body">
<ul class="list" id="list-1">
<li class="element">Foo</li>
<li class="element">Bar</li>
<li class="element">Jay</li>
</ul>
<ul class="list list-small" id="list-2">
<li class="element">Foo</li>
<li class="element">Bar</li>
</ul>
</div>
</div>
'''
from bs4 import BeautifulSoup
soup = BeautifulSoup(html, 'lxml')
print(soup.find_all(name='ul'))
print(type(soup.find_all(name='ul')[0]))
调用find_all方法,向其中传入name参数,其参数值为ul,意思是查询所有ul节点。
列表中每个元素都是bs4.element.Tag类型,依然可以进行嵌套查询。代码:
for ul in soup.find_all(name='ul'):
print(ul.find_all(name='li'))
返回结果还是列表类型,列表中每个元素依然是Tag类型。接下来遍历每个li节点,获取文本内容:
for ul in soup.find_all(name='ul'):
print(ul.find_all(name='li'))
for li in ul.find_all(name='li'):
print(li.string)
2.8.3 attrs
除了根据节点名查询,也可以传入一些属性进行查询:
html = '''
<div class="panel">
<div class="panel-heading">
<h4>Hello</h4>
</div>
<div class="panel-body">
<ul class="list" id="list-1">
<li class="element">Foo</li>
<li class="element">Bar</li>
<li class="element">Jay</li>
</ul>
<ul class="list list-small" id="list-2">
<li class="element">Foo</li>
<li class="element">Bar</li>
</ul>
</div>
</div>
'''
from bs4 import BeautifulSoup
soup = BeautifulSoup(html, 'lxml')
print(soup.find_all(attrs={'id':'lisy-1'}))
print(soup.find_all(attrs={'name':'elements'}))
另一种查询方式:
from bs4 import BeautifulSoup
soup = BeautifulSoup(html, 'lxml')
print(soup.find_all(id="lisy-1"))
print(soup.find_all(class="elements"))
2.8.4 text
text参数可以用来匹配节点的文本,其传入形式可以是字符串,也可以是正则表达式形式,代码:
import re
html='''
<div class="panel">
<div class="panel-body">
<a>Hello, this is a link</a>
<a>Hello, this is a link, too</a>
</div>
</div>
'''
from bs4 import BeautifulSoup
soup = BeautifulSoup(html, 'lxml')
print(soup.find_all(text=re.compile('link')))
这里有两个a节点,其内部包含文本信息,find_all方法中传入text参数,该参数为正则表达式,返回结果是由所有与正则表达式相匹配的节点文本组成的元素。
2.8.5 find
find方法可以查询符合条件的元素,但是find方法返回的是单个元素,也就是第一个匹配的元素,而find_all会返回由所有匹配的元素组成的列表,代码:
html='''
<div class="panel">
<div class="panel-heading">
<h4>Hello</h4>
</div>
<div class="panel-body">
<ul class="list" id="list-1">
<li class="element">Foo</li>
<li class="element">Bar</li>
<li class="element">Jay</li>
</ul>
<ul class="list list-small" id="list-2">
<li class="element">Foo</li>
<li class="element">Bar</li>
</ul>
</div>
</div>
'''
from bs4 import BeautifulSoup
soup = BeautifulSoup(html, 'lxml')
print(soup.find(name='ul'))
print(type(soup.find(name='ul')))
print(soup.find(class_='list'))
2.8.6 其他查询方法
2.9 CSS选择器
使用CSS选择器,只需要调用select方法,传入相应的CSS选择器即可,代码:
html = '''
<div class="panel">
<div class="panel-heading">
<h4>Hello</h4>
</div>
<div class="panel-body">
<ul class="list" id="list-1">
<li class="element">Foo</li>
<li class="element">Bar</li>
<li class="element">Jay</li>
</ul>
<ul class="list list-small" id="list-2">
<li class="element">Foo</li>
<li class="element">Bar</li>
</ul>
</div>
</div>
'''
from bs4 import BeautifulSoup
soup = BeautifulSoup(html, 'lxml')
print(soup.select('.panel .panel-heading'))
print(soup.select('ul li'))
print(soup.select('#list-2 .element'))
print(type(soup.select('ul')[0]))
返回结果均是符合css选择器的节点组成的列表,例如select('ul li')表示选择所有ul节点下面的所有li节点,结果便是所有li节点组成的列表。
2.9.1 嵌套选择
例如,先选择所有ul节点,再遍历每个ul节点,选择其li节点,代码:
from bs4 import BeautifulSoup
soup = BeautifulSoup(html, 'lxml')
for ul in soup.select('ul'):
print(ul.select('li'))
2.9.2 获取属性
from bs4 import BeautifulSoup
soup = BeautifulSoup(html, 'lxml')
for ul in soup.select('ul'):
print(ul['id'])
print(ul.attrs['id'])
2.9.3 获取文本
from bs4 import BeautifulSoup
soup = BeautifulSoup(html, 'lxml')
for li in soup.select('li'):
print('Get Text:', li.get_text())
print('String:', li.string)
2.9.4 总结
3 pyquery
功能更强大的库
3.1 准备工作
pip3 install pyquery
3.2 初始化
在用pyquery库解析html文本的时候,需要先将其初始化为一个PyQuery对象。
3.2.1 字符串初始化
html = '''
<div>
<ul>
<li class="item-0">first item</li>
<li class="item-1"><a href="link2.html">second item</a></li>
<li class="item-0 active"><a href="link3.html"><span class="bold">third item</span></a></li>
<li class="item-1 active"><a href="link4.html">fourth item</a></li>
<li class="item-0"><a href="link5.html">fifth item</a></li>
</ul>
</div>
'''
from pyquery import PyQuery as pq
doc = pq(html)
print(doc('li'))
3.2.2 URL初始化
初始化的参数除了能以字符串形式传递,还能是网页的url,此时只需要指定PyQuery对象的参数url即可:
from pyquery import PyQuery as pq
doc = pq(url='https://cuiqingcai.com')
print(doc('title'))
下面代码实现的功能是相同的:
from pyquery import PyQuery as pq
import requests
doc = pq(requests.get('https://cuiqingcai.com').text)
print(doc('title'))
3.2.3 文件初始化
还可以传递本地的文件名,此时将参数指定为filename即可:
from pyquery import PyQuery as pq
doc = pq(filename = 'demo.html')
print(doc('li'))
3.3 基本CSS选择器
实例:
html = '''
<div>
<ul>
<li class="item-0">first item</li>
<li class="item-1"><a href="link2.html">second item</a></li>
<li class="item-0 active"><a href="link3.html"><span class="bold">third item</span></a></li>
<li class="item-1 active"><a href="link4.html">fourth item</a></li>
<li class="item-0"><a href="link5.html">fifth item</a></li>
</ul>
</div>
'''
from pyquery import PyQuery as pq
doc = pq(html)
print(doc('#container .list li'))
print(type(doc('#container .list li')))
运行结果如下:
采用直接遍历获取的节点,调用text方法,可以直接获取节点的文本内容,代码:
for item in doc('#container .list li').items():
print(item.text())
3.4 查找节点
3.4.1 子节点
查找子节点时,需要用到find方法,其参数时css选择器,代码:
from pyquery import PyQuery as pq
doc = pq(html)
items = doc('.list')
print(type(items))
lis = items.find('li')
print(type(lis))
print(lis)
这里我们选择class为list的节点,然后调用find方法,并将其传入css选择器,选取其内部的li节点,最后打印输出。
此外,如果想只查找子节点,可以用children方法:
lis = items.children()
print(type(lis))
print(lis)
如果要筛选所有子节点中符合条件的节点,例如想筛选出子节点中class为active的节点,则可以向children方法传入css选择器.active,代码:
lis = items.children('.active')
print(lis)
3.4.2 父节点
用parent方法获取某个节点的父节点,代码:
html = '''
<div class='wrap'>
<div id='container'>
<ul class="list">
<li class="item-0">first item</li>
<li class="item-1"><a href="link2.html">second item</a></li>
<li class="item-0 active"><a href="link3.html"><span class="bold">third item</span></a></li>
<li class="item-1 active"><a href="link4.html">fourth item</a></li>
<li class="item-0"><a href="link5.html">fifth item</a></li>
</ul>
</div>
</div>
'''
from pyquery import PyQuery as pq
doc = pq(html)
print(doc('.list'))
container = items.parent()
print(type(container))
print(container)
这里我们首先用.list选取class为list的节点,然后调用parent方法得到其父节点,其类型依然是PyQuery。
如果想获取某个祖先节点,可以用parents方法:
如果想要筛选某个祖先节点,可以向parents方法传入css选择器,就会返回祖先节点中符合css选择器的节点:
parent = items.parents('.wrap')
print(parent)
3.4.3 兄弟节点
获取兄弟节点可以使用siblings方法:
from pyquery import PyQuery as pq
doc = pq(html)
li = doc('.list .item-0.active')
print(li.siblings())
这里首先选择class为list的节点内部的class为item-0和active的节点,也就是第三个li节点。
如果要筛选某个兄弟节点,依然可以向siblings方法中传入css选择器,这样就能从所有兄弟节点中挑选出符合条件的节点:
from pyquery import PyQuery as pq
doc = pq(html)
li = doc('.list .item-0.active')
print(li.siblings('.active'))
运行结果如下:
3.5 遍历节点
pyquery库的选择结果可能是多个节点,也可能是单个节点,类型都是pyquery类型,但不像beautiful soup那样返回列表。
如果结果是单个节点,既可以直接打印输出,也可以直接转成字符串:
from pyquery import PyQuery as pq
doc = pq(html)
li = doc('.item-0.active')
print(li)
print(str(li))
如果结果是多个节点,就可以通过items方法遍历获取:
from pyquery import PyQuery as pq
doc = pq(html)
lis = doc('li').items()
print(type(lis))
for li in lis:
print(li,type(li))
需要获取的信息一般包括属性和文本:
3.5.1 获取属性
html = '''
<div class='wrap'>
<div id='container'>
<ul class="list">
<li class="item-0">first item</li>
<li class="item-1"><a href="link2.html">second item</a></li>
<li class="item-0 active"><a href="link3.html"><span class="bold">third item</span></a></li>
<li class="item-1 active"><a href="link4.html">fourth item</a></li>
<li class="item-0"><a href="link5.html">fifth item</a></li>
</ul>
</div>
</div>
'''
from pyquery import PyQuery as pq
doc = pq(html)
a = doc('.item-0.active')
print(a,type(a))
print(a.attr('href'))
此外,也可以通过调用attr属性来获取属性值:
print(a.attr.href)
如果选中的多个元素,调用attr方法,智慧得到第一个节点的属性:
a = doc('a')
print(a,type(a))
print(a.attr('href'))
print(a.attr.href)
如果想要获取a节点的所有属性,需要遍历:
from pyquery import PyQuery as pq
doc = pq(html)
a = doc('a')
for item in a.items():
print(item.attr('href'))
3.5.2 获取文本
获取节点之后的另一个主要操作就是获取其内部的文本,调用text方法实现:
html = '''
<div class='wrap'>
<div id='container'>
<ul class="list">
<li class="item-0">first item</li>
<li class="item-1"><a href="link2.html">second item</a></li>
<li class="item-0 active"><a href="link3.html"><span class="bold">third item</span></a></li>
<li class="item-1 active"><a href="link4.html">fourth item</a></li>
<li class="item-0"><a href="link5.html">fifth item</a></li>
</ul>
</div>
</div>
'''
from pyquery import PyQuery as pq
doc = pq(html)
a = doc('.item-0.active')
print(a)
print(a.text)
如果想要获取节点内部的html文本,需要用到html方法:
from pyquery import PyQuery as pq
doc = pq(html)
li = doc('.item-0.active')
print(li)
print(li.html())
如果我们选中的是多个节点,html方法返回的是第一个li节点内部的html文本,而text返回了所有li节点内部的纯文本,各节点内部中间用一个空格分隔开,返回结果是一个字符串。
html = '''
<div class='wrap'>
<div id='container'>
<ul class="list">
<li class="item-0">first item</li>
<li class="item-1"><a href="link2.html">second item</a></li>
<li class="item-0 active"><a href="link3.html"><span class="bold">third item</span></a></li>
<li class="item-1 active"><a href="link4.html">fourth item</a></li>
<li class="item-0"><a href="link5.html">fifth item</a></li>
</ul>
</div>
</div>
'''
from pyquery import PyQuery as pq
doc = pq(html)
li = doc('li')
print(li.html())
print(li.text())
print(type(li,text())
3.6 节点操作
pyquery库提供了一系列方法对节点进行动态修改,例如为某个节点添加一个class,移除某个节点等。
3.6.1 addClass和removeClass
html = '''
<div class='wrap'>
<div id='container'>
<ul class="list">
<li class="item-0">first item</li>
<li class="item-1"><a href="link2.html">second item</a></li>
<li class="item-0 active"><a href="link3.html"><span class="bold">third item</span></a></li>
<li class="item-1 active"><a href="link4.html">fourth item</a></li>
<li class="item-0"><a href="link5.html">fifth item</a></li>
</ul>
</div>
</div>
'''
from pyquery import PyQuery as pq
doc = pq(html)
li = doc('.item-0.active')
print(li)
li.removeClass('active')
print(li)
li.addClass('active')
print(li)
3.6.2 attr、text和html
html = '''
<ul class="list">
<li class="item-0 active"><a href="link3.html"><span class="bold">third item</span></a></li>
</ul>
'''
from pyquery import PyQuery as pq
doc = pq(html)
li = doc('.item-0.active')
print(li)
li.attr('name','link')
print(li)
li.text('changed item')
print(li)
li.html('<span>changed item</span>')
print(li)
3.6.3 remove
html = '''
<div class='wrap'>
Hello,world
<p>This is a paragraph.</p>
</div>
'''
from pyquery import PyQuery as pq
doc = pq(html)
wrap = doc('.wrap')
print(wrap.text())
3.7 伪类选择器
html = '''
<div class='wrap'>
<div id='container'>
<ul class="list">
<li class="item-0">first item</li>
<li class="item-1"><a href="link2.html">second item</a></li>
<li class="item-0 active"><a href="link3.html"><span class="bold">third item</span></a></li>
<li class="item-1 active"><a href="link4.html">fourth item</a></li>
<li class="item-0"><a href="link5.html">fifth item</a></li>
</ul>
</div>
</div>
'''
from pyquery import PyQuery as pq
doc = pq(html)
# 选择第一个 li 节点
li = doc('li:first-child')
print("第一个 li 节点:")
print(li)
print()
# 选择最后一个 li 节点
li = doc('li:last-child')
print("最后一个 li 节点:")
print(li)
print()
# 选择第奇数个 li 节点
li = doc('li:nth-child(odd)')
print("第奇数个 li 节点:")
print(li)
print()
# 选择第偶数个 li 节点
li = doc('li:nth-child(even)')
print("第偶数个 li 节点:")
print(li)
print()
# 选择包含文本 "second" 的 li 节点
li = doc('li:contains("second")')
print('包含文本 "second" 的 li 节点:')
print(li)
print()
# 选择类名为 "active" 的 li 节点
li = doc('li.active')
print('类名为 "active" 的 li 节点:')
print(li)
print()
# 选择有子元素 a 的 li 节点
li = doc('li:has(a)')
print('有子元素 a 的 li 节点:')
print(li)
print()
4 parsel 的使用
4.1 提取数据
使用 selector.css('.item-0')
提取所有类名为 item-0
的 <li>
元素。使用 selector.xpath('//li[contains(@class, "item-0")]')
提取所有包含 item-0
类的 <li>
元素。打印提取到的元素数量、类型和内容。
html = '''
<div>
<ul>
<li class="item-0">first item</li>
<li class="item-1"><a href="link2.html">second item</a></li>
<li class="item-0 active"><a href="link3.html"><span class="bold">third item</span></a></li>
<li class="item-1 active"><a href="link4.html">fourth item</a></li>
<li class="item-0"><a href="link5.html">fifth item</a></li>
</ul>
</div>
'''
from parsel import Selector
selector = Selector(text=html)
items = selector.css('.item-0')
print(len(items), type(items), items)
items2 = selector.xpath('//li[contains(@class, "item-0")]')
print(len(items2), type(items), items2)
4.2 提取文本内容
使用 CSS 选择器 .item-0
提取所有类名为 item-0
的 <li>
元素。遍历提取到的元素,使用 XPath 表达式 .//text()
提取每个元素及其子元素中的所有文本内容。使用 CSS 选择器 .item-0 *::text
提取所有 .item-0
元素的子元素中的文本节点,并获取所有结果。
html = '''
<div>
<ul>
<li class="item-0">first item</li>
<li class="item-1"><a href="link2.html">second item</a></li>
<li class="item-0 active"><a href="link3.html"><span class="bold">third item</span></a></li>
<li class="item-1 active"><a href="link4.html">fourth item</a></li>
<li class="item-0"><a href="link5.html">fifth item</a></li>
</ul>
</div>
'''
from parsel import Selector
selector = Selector(text=html)
items = selector.css('.item-0')
for item in items:
text = item.xpath('.//text()').get()
print(text)
# result = selector.xpath('//li[contains(@class, "item-0")]//text()').get()
# print(result)
result = selector.css('.item-0 *::text').getall()
print(result)
# result = selector.css('.item-0::text').get()
# print(result)
4.3 提取特定的文本内容
selector.xpath('//li[contains(@class, "item-0")]//text()')
使用 XPath 表达式提取所有 <li>
元素中包含 item-0
类的文本内容。//li[contains(@class, "item-0")]
:匹配所有 <li>
元素,其 class
属性包含 item-0
。//text()
:提取匹配到的 <li>
元素及其子元素中的所有文本节点。.getall()
获取所有提取到的文本节点,返回一个列表。
html = '''
<div>
<ul>
<li class="item-0">first item</li>
<li class="item-1"><a href="link2.html">second item</a></li>
<li class="item-0 active"><a href="link3.html"><span class="bold">third item</span></a></li>
<li class="item-1 active"><a href="link4.html">fourth item</a></li>
<li class="item-0"><a href="link5.html">fifth item</a></li>
</ul>
</div>
'''
from parsel import Selector
selector = Selector(text=html)
result = selector.xpath('//li[contains(@class, "item-0")]//text()').getall()
print(result)
# result = selector.css('.item-0::text').get()
# print(result)
4.4 提取特定的属性值
selector.xpath('//li[contains(@class, "item-0") and contains(@class, "active")]/a/@href')
使用 XPath 表达式提取同时具有 item-0
和 active
类的 <li>
元素中的 <a>
标签的 href
属性值。//li[contains(@class, "item-0") and contains(@class, "active")]
:匹配同时具有 item-0
和 active
类的 <li>
元素。/a/@href
:提取 <a>
标签的 href
属性值。.get()
获取提取到的第一个属性值。
html = '''
<div>
<ul>
<li class="item-0">first item</li>
<li class="item-1"><a href="link2.html">second item</a></li>
<li class="item-0 active"><a href="link3.html"><span class="bold">third item</span></a></li>
<li class="item-1 active"><a href="link4.html">fourth item</a></li>
<li class="item-0"><a href="link5.html">fifth item</a></li>
</ul>
</div>
'''
from parsel import Selector
selector = Selector(text=html)
result = selector.css('.item-0.active a::attr(href)').get()
print(result)
result = selector.xpath('//li[contains(@class, "item-0") and contains(@class, "active")]/a/@href').get()
print(result)
4.5 提取属性
对于每个匹配到的 .item-0
元素,正则表达式 link.*
会匹配其文本内容中以 "link" 开头的部分。具体来说:
第一个
.item-0
元素的文本是 "first item",不匹配正则表达式。第二个
.item-0
元素的文本是 "third item",不匹配正则表达式。第三个
.item-0
元素的文本是 "fifth item",不匹配正则表达式。
html = '''
<div>
<ul>
<li class="item-0">first item</li>
<li class="item-1"><a href="link2.html">second item</a></li>
<li class="item-0 active"><a href="link3.html"><span class="bold">third item</span></a></li>
<li class="item-1 active"><a href="link4.html">fourth item</a></li>
<li class="item-0"><a href="link5.html">fifth item</a></li>
</ul>
</div>
'''
from parsel import Selector
selector = Selector(text=html)
result = selector.css('.item-0').re('link.*')
print(result)
4.6 正则提取
selector.css('.item-0')
提取所有类名为 item-0
的 <li>
元素。.re_first('<span class="bold">(.*?)</span>')
使用正则表达式 <span class="bold">(.*?)</span>
提取匹配的内容,并返回第一个匹配结果。<span class="bold">(.*?)</span>
:匹配 <span class="bold">
开头,</span>
结尾的内容,并捕获中间的任意字符(非贪婪模式)。
html = '''
<div>
<ul>
<li class="item-0">first item</li>
<li class="item-1"><a href="link2.html">second item</a></li>
<li class="item-0 active"><a href="link3.html"><span class="bold">third item</span></a></li>
<li class="item-1 active"><a href="link4.html">fourth item</a></li>
<li class="item-0"><a href="link5.html">fifth item</a></li>
</ul>
</div>
'''
from parsel import Selector
selector = Selector(text=html)
result = selector.css('.item-0').re_first('<span class="bold">(.*?)</span>')
print(result)
来源: