Joeyos's Blog Software Engineer

爬虫解析库

2019-05-09
Quan Zhang

前面实现了一个最基本的爬虫,但在提取页面信息时使用的是正则表达式,这比较繁琐,一旦写错,可能导致匹配失败。对于网页的节点来说,可以定义为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')
  • 获取文本:
  1. //text(),夹杂换行符
  2. /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节点

Similar Posts

下一篇 爬虫数据存储

Comments