爬虫解析库(8. lxml和XPath)

一、内容介绍

尽管正则表达式很厉害,但是写出功能强大的正则表达式不容易,而且遇到不同的页面就要重写,难以维护(确实)
Xpath非常容易理解的路径方式选择XML和HTML中的节点,容易维护和编写

本章主要内容:
    1、安装lxml
    2、用lxml操作XML和HTML文档
    3、XPath的基本概念
    4、用XPath选取节点(所有节点、子节点、父节点等)
    5、用Xpath匹配和选取属性
    6、按序选取节点
    7、节点轴
    8、实战案例,演示使用requests和XPath花去和分析HTML代码

二、知识点干货

8.1 解析xml文件实例

  1. product.xml文件:
<products>
    <product id = "0001">
        <name>手机</name>
        <price>4500</price>
    </product>
    <product id = "0002">
        <name>电脑</name>
        <price>6000</price>
    </product>
</products>
  1. py解析文件:
from lxml import etree
# 读取products.xml文件
tree = etree.parse('products.xml')
print(type(tree))
# 将tree重新转换为字符串形式的XML文档,并输出
print(str(etree.tostring(tree,encoding="utf-8"),'utf-8'))
# 获得根节点对象
root = tree.getroot()
print(type(root))
# 输出根节点名称
print('root:',root.tag)
# 获得根节点的所有子节点
children = root.getchildren()
print('----------输出产品信息----------')
# 迭代这些子节点,并输出对应的属性和节点文本
for child in children:
    print('product id = ', child.get('id'))
    print('child[0].name:',child[0].text)
    print('child[1].price:', child[1].text)
# 分析字符串形式的XML文档
root = etree.fromstring('''
<products>
    <prduct1 name='iPhone'/>
    <prduct2 name='iPAD'/>
</products>
''')
print('-----新产品-----')
# 输出根节点的节点名
print('root=',root.tag)
children = root.getchildren()
# 迭代这些子节点,并输出节点的名称和name属性名
for child in children:
    print(child.tag,'name = ',child.get('name'))

8.2 解析html实例

读取test.html的HTML文件,并输出根节点以及其他节点的属性值和文本

  1. test.html文件
<html lang="en"><head><meta charset="UTF-8">
    <title>通过XPath过滤的HTML文档</title>
</head>
<body>
</body>
</html>
  1. py解析文件
from lxml import etree
# 创建lxml.etree.HTMLParser对象
parser = etree.HTMLParser()
print(type(parser))
# 读取并解析test.html文件
tree = etree.parse('test.html', parser)
# 获取根节点
root = tree.getroot()
# 将html文档转换为可读模式
result = etree.tostring(root,encoding='utf-8',pretty_print=True,method="html")
print(str(result,'utf-8'))
# 输出根节点的名称
print(root.tag)
# 输出根节点的lang属性的值
print('lang =',root.get('lang'))
# 输出meta节点的charset属性的值
print('charset =',root[0][0].get('charset'))
# 输出title节点的文本
print('charset =',root[0][1].text)

8.3 lxml库和XPath结合实例

lxml装载HTML代码 有两种方式
        1、从文件装载,通过parse函数指定HTML文件名
        2、从代码装载,通过HTML函数指定HTML代码

Xpath语法规则:
		 nodename    :选取此节点的所有子节点
            /           :从当前节点选取直接子节点
            //          :从当前节点选取子孙节点
            .           :选取当前节点
            ..          :选取当前节点的父节点
            @           :选取属性
  1. 使用lxml库和XPath选取test.html文件中标题
from lxml import etree
parser = etree.HTMLParser()
tree = etree.parse('test.html',parser)
# 使用XPath定位title节点,返回一个节点集合
titles = tree.xpath('/html/head/title')
if len(titles) > 0:
    # 输出title节点的文本
    print(titles[0].text)
  1. 一段HTML代码中特定节点中的href属性值和节点文本
# 定义一段HTML代码
html = '''
<div>
    <ul>
        <li class="item1"><a href="https://geekori.com">geekori.com</a></li>
        <li class="item2"><a href="https://www.jd.com">京东商城</a></li>
        <li class="item3"><a href="https://www.taobao.com">淘宝</a></li>
    </ul>
</div>
'''
# 分析HTML代码
tree = etree.HTML(html)
# 使用XPath定位class属性值为item2的<li>节点
aTags = tree.xpath("//li[@class='item2']")
if len(aTags)>0:
    # 得到该<li>节点中<a>节点的href属性值和文本
    print(aTags[0][0].get('href'),aTags[0][0].text)

8.4 allnode获取所有节点

语法规则:
		//   会选取所有符合要求的节点
        //*  会选取整个HTML文档中所有的节点
        //li 会选取所有的<li>节点
  1. demo.html文件内容
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>XPath演示</title>
</head>
<body>
<div>
    <ul>
        <li class="item1"><a href="https://geekori.com"> geekori.com</a></li>
        <li class="item2"><a href="https://www.jd.com"> 京东商城</a></li>
        <li class="item3"><a href="https://www.taobao.com">淘宝</a></li>
        <li class="item4" value="1234"><a href="https://www.microsoft.com">微软</a></li>
        <li class="item5"><a href="https://www.google.com">谷歌</a></li>
    </ul>
</div>
</body>
</html>
  1. py解析文件
from lxml import etree
parser = etree.HTMLParser()
html = etree.parse('demo.html', parser)
# 选取demo.html文件中所有的节点
nodes = html.xpath('//*')
print('共',len(nodes),'个节点')
print(nodes)
# 输出所有节点,indent是缩进
def printNodeTree(node,indent):
    print(indent + node.tag)
    indent += "    "
    children = node.getchildren()
    if len(children) > 0:
        for i in range(0, len(children)):
            # 递归调用
            printNodeTree(children[i],indent)
print()
# 按层次输出节点的节点,nodes[0]是根节点(html节点)
printNodeTree(nodes[0],"")
# 选取demo.html文件汇总所有的<a>节点
nodes = html.xpath('//a')
print()
print('共', len(nodes), '个<a>节点')
print(nodes)
# 输出所有<a>节点的文本
for i in range(0, len(nodes)):
    print(nodes[i].text, end=' ')

解析文件中用了递归调用的方式,可以看到html文件中节点的结构

8.5 childnode节点

使用XPath根据不同的规则选取demo.html文件中所有<a>节点,并输出<a>节点的文本
值得注意的是 孙子节点子节点后代节点 的区别

from lxml import etree
parser = etree.HTMLParser()
html = etree.parse('demo.html', parser)

# 成功选取<a>节点
# //直接选取此节点,/直接选取此节点下的子节点
nodes = html.xpath('//li/a')
print("共{}个节点".format(len(nodes)))
print(nodes)
for i in range(0,len(nodes)):
    print(nodes[i].text,end=' ')

print()
# 成功选取<a>
# //直接选取此节点,但a不是ul的子节点,所以再用一个//a选取ul节点下的所有a节点
nodes = html.xpath('//ul//a')
print("共{}个节点".format(len(nodes)))
print(nodes)
for i in range(0,len(nodes)):
    print(nodes[i].text,end=' ')

print()
# 无法选取<a>节点,因为<a>不是<ul>的直接子节点
nodes = html.xpath('//ul/a')
print(nodes)
for i in range(0,len(nodes)):
    print(nodes[i].text,end=' ')

8.6 parentnode父节点

使用XPath选取特定<a>节点的父节点(<li>节点),并输出父节点的class属性值
        知道子节点,得到父节点的方法:
            1、可以使用..
            2、也可以使用parent::*

使用demo.html测试上述规则,案例如下:

from lxml import etree
parser = etree.HTMLParser()
html = etree.parse('demo.html',parser)
# 选取href属性值为https://www.jd.com的<a>节点的父节点,并输出父节点的class属性值
result = html.xpath('//a[@href="https://www.jd.com"]/../@class')
print('class属性 =',result)
# # 选取href属性值为https://www.jd.com的<a>节点的父节点,并输出父节点的class属性值
result = html.xpath('//a[@href="https://www.jd.com"]/parent::*/@class')
print('class属性 =',result)

8.7 property获取节点属性

使用XPath根据<a>节点的href属性过滤特定的<a>节点,并输出<a>节点的文本和URL
        XPath的过滤条件需要放到一对中括号([...])中,
          1、'//a[@class="item1"]'表示过滤所有class属性值为item1的<a>节点
          2、如不将属性引用放在[...]中,就是获取属性值,如'//a/@href'表示获取所有<a>节点的href的值

使用demo.html测试上述规则,案例如下:

from lxml import etree

parser = etree.HTMLParser()
html = etree.parse('demo.html',parser)
# 选取所有href属性包含https://geekori.com的<a>节点
nodes = html.xpath('//a[@href="https://geekori.com"]')
print('共{}节点'.format(len(nodes)))
for i in range(0,len(nodes)):
    print(nodes[i].text)

# 选取所有href属性值包含www的<a>节点
nodes = html.xpath('//a[contains(@href,"www")]')
print('共{}节点'.format(len(nodes)))
for i in range(0,len(nodes)):
    print(nodes[i].text)

# 获取所有href属性值包含www的<a>节点的href属性值,urls是href属性值的列表
urls = html.xpath('//a[contains(@href,"www")]/@href')
for i in range(0,len(urls)):
    print(urls[i])

8.8 multiproperties多条件选择节点

  1. and和or关键字以及contains函数的使用,注意大小写敏感,不能写成And或者AND
  2. 用and和or选取也定的<a>节点,并输出<a>节点的文本和标题
from lxml import etree
parser = etree.HTMLParser()
html = etree.parse('demo.html', parser)
# 选取href属性值为https://www.jd.com或https://geekori.com的<a>节点
aList = html.xpath('//a[@href="https://www.jd.com" or @href="https://geekori.com"]')
for a in aList:
    print(a.text,a.get('href'))

# 匹配<li class="item4" value="1234"><a href="https://www.microsoft.com">微软</a></li>
# 选取href属性值包含www,且父节点中value属性值等于1234的<a>结点
print("---------------------------")
aList = html.xpath('//a[contains(@href,"www") and ../@value="1234"]')
for a in aList:
    print(a.text,a.get('href'))

😕

# 再补充一些XPath中的其他的运算符
# |   :   计算节点集   ://book|//cd   :  返回所有拥有book和cd元素的节点集
# +、-、*、div、=、!=、<、<=、>、>=、or、and、mod

8.9 ordernode获取某个节点的规则

使用position()选取第几个节点,使用last()选取最后一个结点

from lxml import etree
parser = etree.HTMLParser()
text = '''
<div>
   <a href="https://geekori.com"> geekori.com</a>
   <a href="https://www.jd.com"> 京东商城</a>
   <a href="https://www.taobao.com">淘宝</a>
   <a href="https://www.microsoft.com">微软</a>
   <a href="https://www.google.com">谷歌</a>
</div>
'''
html   = etree.HTML(text)

# 选择第1个<a>节点
a1 = html.xpath('//a[1]/text()')
# 选择第2个<a>节点
a2 = html.xpath('//a[position()=2]/text()')
print(a1,a2)
# 选择最后一个<a>节点
lasta = html.xpath('//a[last()]/text()')
print(lasta)
# 选择索引大于3的<a>节点
aList = html.xpath('//a[position() > 3]/text()')
print(aList)
# 选择第2个<a>节点和倒数第2个<a>节点
aList = html.xpath('//a[position()=2 or position() = last()-1]/text()')
print(aList)

8.10 nodeaxis节点轴获取特定节点方法

常用节点轴:
	ancestor轴,用于获取某节点的所有祖先节点
	attribute轴,用于获取某节点的所有属性值
	child轴,用于获取某节点的所有子节点
	descendant轴,用于获取某节点的所有子孙节点
	following轴,用于获取某节点后的所有子节点(包括子孙节点)
	following-sibling轴,用于获取某节点后所有同级的节点

使用XPAth和不同的节点轴选择方法得到特定的结点,并输出节点的文本

from lxml import etree
parser = etree.HTMLParser()
text = '''
<html>
<head>
    <meta charset="UTF-8">
    <title>XPath演示</title>
</head>
<body class="item">
<div>
    <ul class="item" >
        <li class="item1"><a href="https://geekori.com"> geekori.com</a></li>
        <li class="item2"><a href="https://www.jd.com">京东商城</a>
                            <value url="https://geekori.com"/>
                            <value url="https://www.google.com"/>
        </li>
        <li class="item3"><a href="https://www.taobao.com">淘宝</a>
                          <a href="https://www.tmall.com/">天猫</a></li>
        <li class="item4" value="1234"><a href="https://www.microsoft.com">微软</a></li>
        <li class="item5"><a href="https://www.google.com">谷歌</a></li>
    </ul>
</div>
</body>
</html>
'''
html = etree.HTML(text)
# 使用ancestor轴,用于获取所有祖先结点。后面必须跟两个冒号(::),然后是节点选择器
# 这里的*表示匹配所有的结点
result = html.xpath('//li[1]/ancestor::*')
# 输出结果:html body div ul
for value in result:
    print(value, end=" ")

print()
# 使用ancestor轴匹配所有的class属性值为item的祖先节点
result = html.xpath('//li[1]/ancestor::*[@class="item"]')
# 输出结果body ul
for value in result:
    print(value, end=" ")

print()
# 使用attribute轴获取第4个<li>节点的所有属性值
result = html.xpath('//li[4]/attribute::*')
# 输出结果:['item4','1234']
print(result)

print()
# 使用child轴获取第3个<li>节点的所有子节点
result = html.xpath('//li[3]/child::*')
# https://www.taobao.com 淘宝 https://www.tmall.com/ 天猫
for value in result:
    print(value.get('href'), value.text, end=" ")

print()
# 使用descendant轴获取第2个<li>节点的所有名为value的子孙节点
result = html.xpath('//li[2]/descendant::value')
# 输出结果:https://geekori.com https://www.google.com
for value in result:
    print(value.get('url'),end=" ")

print()
# 使用following轴获取第1个<li>节点后的所有子节点(包括子孙节点)
result = html.xpath('//li[1]/following::*')
# 输出结果 li a value value li a a li a li a
for value in result:
    print(value.tag,end=" ")

print()
# 使用following轴获取第1个<li>节点后位置大于4的所有子节点
result = html.xpath('//li[1]/following::*[position()>4]')
# 输出结果 li a a li a li a
for value in result:
    print(value.tag,end=" ")

print()
# 使用following-sibling轴获取第1个<li>节点后所有同级的节点
result = html.xpath('//li[1]/following-sibling::*')
# 输出结果 li item2 li item3 li item4 li item5
for value in result:
    print(value.tag,value.get('class'),end=" ")

8.11 autoXpath

  1. 在Chrome浏览器中通过开发者工具获取京东商城首页与导航条对应的XPath代码,并稍加修改,然后利用requests库抓取导航条文本
  2. 进入www.jd.com选取秒杀那一行,然后右键Copy,选取XPath,有’//*[@id=“navitems-group1”]/li[1]/a’
  3. 本例需要抓取所有符合条件的<a>节点,而通过开发者工具获取XPath代码只是选择了第1个<li>节点中的,<a>节点,做如下修改
    ‘//*[@id=“navitems-group1”]//a/’
import requests
from lxml import etree
# 抓取京东商城首页的HTMl代码
r = requests.get('https://www.jd.com')
parser = etree.HTMLParser()
html = etree.HTML(r.text)
# 提取导航条第1部分的链接文本
nodes = html.xpath('//*[@id="navitems-group1"]//a/text()')
print(nodes)
# 提取导航条第2部分的链接文本
nodes = html.xpath('//*[@id="navitems-group2"]//a/text()')
print(nodes)
# 提取导航条第3部分的链接文本
nodes = html.xpath('//*[@id="navitems-group3"]//a/text()')
print(nodes)

输出的结果打印:

['秒杀 ', '优惠券 ', 'PLUS会员 ', '品牌闪购']
['拍卖 ', '京东家电 ', '京东超市', '京东生鲜']
['京东国际 ', '京东云 ']

实战案例:豆瓣图书排行榜Top250

  1. 分析豆瓣读书排行榜页面代码的规律
    https://book.douban.com/top250?start=0
    https://book.douban.com/top250?start=25
  2. 打开页面提取图书标题所在XPath://*[@id=“content”]/div/div[1]/div/table[1]/tbody/tr/td[2]/div[1]/a
    <tr class="item>"中存有需要的信息,可以直接选择tr节点,即//tr[@class=“item”]
    图书名称:td/div/a/@title
    图书主页链接:tf/div/a/@href
  3. 关于Python的内容不再做解释,需要复习的传送门==>>Python学习笔记合集.
from lxml import etree
from lxml import *
import requests
import json
# 根据URL抓取HTML代码,并返回这些代码,如果抓取失败,返回None
def getOnePage(url):
    try:
        res = requests.get(url,headers=headers)
        if res.status_code == 200:
            print('获取成功,页面长度:',len(res.text))
            return res.text
        return None
    except Exception:
        return None
# 分析HTML代码,这是一个生产期函数
def parseOnePage(html):
    selector = etree.HTML(html)
    # 选择<tr>节点
    items = selector.xpath('//tr[@class="item"]')
    # 在<tr>节点内部继续使用XPath选择对应的节点
    for item in items:
        # 这里使用try-except的原因是,有一本书没有作者,只有出版社、日期、价格,会抛出异常
        # except中写continue可以跳过爆出一场的那一条目
        try:
            # 获取<p>节点中的文本,其中包括出版社、作者、出版日期等信息
            book_infos = item.xpath('td/p/text()')
            yield {
                # 获取图书名称
                'name': item.xpath('td/div/a/@title')[0],
                # 获取图书主页链接
                'url': item.xpath('td/div/a/@href')[0],
                # 获取图书作者
                'author': book_infos[0].split('/')[-4],
                # 获取图书出版社
                'publisher': book_infos[0].split('/')[-3],
                # 获取出版日期
                'date': book_infos[0].split('/')[-2],
                # 获取图书价格
                'price': book_infos[0].split('/')[-1]
            }
        except Exception:
            continue
# 将抓到的数据(JSON)格式保存到top250.txt中
def save(content):
    with open('top250books.txt', 'a+', encoding='utf-8') as f:
        f.write(json.dumps(content, ensure_ascii=False) + '\n')
# 抓取url对应的页面,并将页面内容保存到文本中
def getTop250(url):
    html = getOnePage(url)
    for item in parseOnePage(html):
        print(item)
        save(item)

# 设置请求头(User-Agent),否则无法获取页面内容
headers = {
    'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) '
                  'AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.119 Safari/537.36',
}
# 产生10个URL,分别对应TOP250排行榜10个页面的URL
urls = ['https://book.douban.com/top250?start={}'.format(str(i)) for i in range(0,250,25)]
# 循环抓取TOp250排行榜的10个页面的图书信息
for url in urls:
    getTop250(url)

抓取结果:
在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值