前面实现了一个最基本的爬虫,但在提取页面信息时使用的是正则表达式,这比较繁琐,一旦写错,可能导致匹配失败。对于网页的节点来说,可以定义为id,class或者其他属性,而且节点有层次关系,可以通过XPath或者CSS选择器来定位一个或多个节点。
XPath
表达式 | 描述 |
---|---|
nodename | 选取此节点的所有子节点 |
/ | 从当前节点选取直接子节点 |
// | 从当前节点选取子孙节点 |
. | 选取当前节点 |
.. | 选取父节点 |
@ | 选取属性 |
选取所有名称为title,同时属性lang的值为eng的节点。
//title[@lang='eng']
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-0"><a href="link3.html">third item</a></li>
</ul>
</div>
'''
html=etree.HTML(text)
res=etree.tostring(html)
print(res.decode('utf-8'))
#读取html
html=etree.parse('./test.html',etree.HTMLParser())
#匹配所有节点
res=html.xpath('//*')
print(res)
输出:
[<Element html at 0x9397048>,
<Element body at 0x9397248>,
<Element div at 0x9397288>,
<Element ul at 0x93971c8>,
<Element li at 0x9397208>,
<Element a at 0x93972c8>,
<Element li at 0x9397308>,
<Element a at 0x9397348>,
<Element li at 0x9397388>,
<Element a at 0x93970c8>]
- 匹配所有li节点:
res=html.xpath('//li')
- 匹配直接子节点:
res=html.xpath('//li/a')
- 匹配所有子孙节点(非直接关系):
res=html.xpath('//ul//a')
- 匹配父节点:
#获取link2属性,再获取父节点,在获取父节点class属性,得到item-1
res=html.xpath('//a[@href="link2.html"]/../@class')
- 获取文本:
- //text(),夹杂换行符
- /a/text()
res=html.xpath('//li[@class="item-0"]//text()')
print(res)
#输出['first item','third item','\n ']
res=html.xpath('//li[@class="item-0"]/a/text()')
print(res)
#输出['first item','third item']
- 获取属性:
res=html.xpath('//li/a/@href')
#输出['link1.html','link2.html','link3.html']
- 属性多值匹配(contains):
#<li class="li li-first"><a href="link.html">first item</a></li>
res=html.xpath('//li[contains(@class,"li")]/a/text()')
- 多属性匹配(and):
#<li class="li li-first" name="item"><a href="link.html">first item</a></li>
res=html.xpath('//li[contains(@class,"li") and @name="item"]/a/text()')
- 限制节点数量:
res=html.xpath('//li[1]/a/text()')
res=html.xpath('//li[last()]/a/text()')
res=html.xpath('//li[position()<3]/a/text()')
res=html.xpath('//li[last()-2]/a/text()')
- 节点轴选择:
res=html.xpath('//li[1]/ancestor::*')#第一个li的所有祖先节点
res=html.xpath('//li[1]/ancestor::div')#第一个li的div祖先节点
res=html.xpath('//li[1]/attribute::*')#li节点的所有属性值
res=html.xpath('//li[1]/child::a[@href="link1.html"]')#获取指定属性的a节点
res=html.xpath('//li[1]/following-sibling::*')#获取所有后续同级节点
Beautiful Soup库
html="""
<html>
<head>
<title>Blog</title>
</head>
<body>
<p class="title" name="dromouse"><b>The Dormouse's story</b></p>
<p class="story">abcdefg
<a href="https://www.baidu.com" class="sister" id="link1">Demo1</a>
<a href="https://www.baidu.com" class="brother" id="link2">Demo2</a>
<a href="https://www.baidu.com" class="father" id="link3">Demo3</a>
</p>
"""
from bs4 import BeautifulSoup
soup=BeautifulSoup(html,'lxml')
print(soup.prettify())
print(soup.title.string)
输出:
<html>
<head>
<title>
My Blog
</title>
</head>
<body>
<p class="title" name="dromouse">
<b>
The Dormouse's story
</b>
</p>
<p class="story">
abcdefg
<a class="sister" href="https://www.baidu.com" id="link1">
Demo1
</a>
<a class="brother" href="https://www.baidu.com" id="link2">
Demo2
</a>
<a class="father" href="https://www.baidu.com" id="link3">
Demo3
</a>
</p>
</body>
</html>
My Blog
获取节点
- 获取title:
#My Blog
print(soup.title.string)
- 获取名称:
#title
print(soup.title.name)
- 获取属性:
print(soup.p.attrs)
print(soup.p['name'])
#{'class':['title'],'name':'dromouse'}
#dromouse
- 获取内容:
print(soup.p.string)
#获取第一个p节点的文本The Dormouse's story
嵌套选择
print(soup.head.title.string)
#My Blog
关联选择
- 子节点(contents):
print(soup.p.contents)
for i,child in enumerate(soup.p.children):
print(child)
- 得到所有子孙:
for i,child in enumerate(soup.p.descendants):
print(child)
- 父节点:
print(soup.a.parent)
- 兄弟结点:
print(soup.a.next_sibling)
方法选择器
API: find_all(name, attrs, recursive, text, **kwargs)
- 根据节点名查询:
print(soup.find_all(name='ul'))
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)
- 根据属性名查询:
print(soup.find_all(attrs={'id':'list-1'}))
print(soup.find_all(id = 'list-1'))
print(soup.find_all(class_ = 'element'))
- 查询包含指定文本的文本:
print(soup.find_all(text=re.compile('baidu')))
#输出包含baidu字符的所有文字
CSS选择器
调用select方法,传入相应的CSS选择器即可:
print(soup.select('.index .left .menu'))
print(soup.select('ul li'))
print(soup.select('#list-2 .element'))
- 嵌套选择:
for ul in soup.select('ul'):
print(ul.select('li'))
- 获取文本:
print(li.string)
pyquery
Beautiful soup库的CSS选择器有点鸡肋,pyquery的选择器更加强大,类似于jQuery。
用字符串初始化:
html="""
<html>
<head>
<title>Blog</title>
</head>
<body>
<p class="title" name="dromouse"><b>The Dormouse's story</b></p>
<p class="story">abcdefg
<a href="https://www.baidu.com" class="sister" id="link1">Demo1</a>
<a href="https://www.baidu.com" class="brother" id="link2">Demo2</a>
<a href="https://www.baidu.com" class="father" id="link3">Demo3</a>
</p>
<ul>
<div id="container">
<ul class="list">
<li class="item-0 active"><a href="link.html">first item</li>
<li class="item-1"><a href="link.html">second item</li>
<li class="item-2 active"><a href="link.html">third item</li>
</ul>
</div>
"""
from pyquery import PyQuery as pq
doc = pq(html)
print(doc('li'))
- 用网址初始化:
doc = pq(url='https://www.baidu.com')
print(doc('title'))
- 用文件初始化:
doc = pq(filename='demo.html')
print(doc('li'))
CSS选择器
print(doc('#container .list li'))
查找节点
- 子节点:
items = doc('.list')
li=items.find('li')
print(li)
li = items.children()
- 筛选class为.active的节点:
li = items.children('.active')
- 父节点:
pa = items.parent()
- 某个祖先节点:
pa = items.parents('.wrap')
- 兄弟节点:
li = doc('.list .item-0.active')
print(li.siblings())
- 筛选兄弟节点:
print(li.siblings('.active'))
- 遍历:
lis = doc('li').items()
for li in lis:
print(li)
获取信息
- 获取属性:
a=doc('.item-0.active a')
print(a.attr('href'))#获取href值
#link.html
或者:
print(a.attr.href)
#link.html
- 多节点属性:
#获取全部a节点的属性
a=doc('a')
for it in a.items():
print(it.attr('href'))
- 获取文本text()方法:
a=doc('.item-0.active a')
print(a.text())
节点增删改
- addClass和removeClass
li.removeClass('active')
li.addClass('active')
- attr,text和html
li.attr('name','link')#将name对应值改为link
li.text('changed item')#修改节点内部文字内容
li.html('<span>changed item</span>')#修改节点内部内容
- remove
<div class="warp">
Hello World
<p>This is a paragraph.</p>
</div>
只需要提取hello world,不需要p节点:
warp=doc('.warp')
print(warp.text())#包含p节点文字
warp.find('p').remove()
print(warp.text())#不包含p节点文字
伪类选择器
CSS选择器支持多样的伪类选择器,如:选择第一个节点、最后一个节点、奇偶数节点、包含某一文本的节点等。
li=doc('li:first-child')#第一个节点
li=doc('li:last-child')#最后节点
li=doc('li:nth-child(2)')#第二个节点
li=doc('li:gt(2)')#第三个li之后的节点
li=doc('li:nth-child(2n)')#偶数节点
li=doc('li:contains(second)')#包含second的li节点