python处理文档对象【三方库—lxml】

前言

最近在处理网络爬虫下载的网页,使用到lxml模块。对于这个模块一直比较好奇,标准库中已经有xml模块了,为啥还有写一个三方的lxml模块?平常使用中常用作对HTML文件的解析与操作,还能干啥?等等,一大堆问题。特意花费一个下午,了解下它神秘面纱下到底隐藏这个什么。


提示:学习这个模块,建议大家先了解下W3C标准中的DOM对象,这对于大家去理解PAI比较有帮助。以下图片图,因为在学习中有一次让自己发散了,比较open,姑且记下来 ^V^-^V^:

在这里插入图片描述

一、lxml是什么?

lxml是python一个三方库,底层使用C实现,相较于python标准库中xml,具备更优越的性能,可以用来处理比较大的DOM对象(xml、html)。
在这里插入图片描述

二、使用步骤

1.安装lxml模块

pip install lxml

2.lxml模块完整分析

代码业务结构图:重点关注如下两个方法

  • def HTML(text, parser) -> typing.Any 从字符串常量解析HTML文档
  • def XML(text, parser) -> typing.Any 从字符串常量解析XML文档或片段
  • def fromstring(text, parser)-> typing.Any 转换字符串为 DOM对象
  • def parse(source, parser) -> typing.Any 解析文件为DOM对象

在这里插入图片描述

2.超文本标记语言中xpath表达式的介绍

符号说明适用方法描述
nodename根据当前节点名称查询ele.xpath('head')查询当前节点下的所有head名称节点
/从当前节点开始查询,路径ele.xpath('/html/body')查询当前节点下,/html/body所有节点
//查询所有节点,从root节点开始ele.xpath('//div')查询当前节点下所有div节点
.代表当前节点ele.xpath('.')返回当前节点
..代表父节点ele.xpath('..')返回当前节点的父节点
@返回某个属性的节点ele.xpath('//div[@class="cont"]')返回所有class=cont属性的div节点
*返回所有节点ele.xpath('*')返回当前节点下所有子节点
@*返回所有属性的节点ele.xpath('//div[@*]')返回所有具有属性的div节点
node()返回所有节点ele.xpath('node()')返回当前节点下的所有节点,包括换行符号

3.针对lxml基类 <class ‘lxml.etree._Element’>的API

描述api说明
节点增删def addnext(self, element) -> typing.Any将元素直接添加为该元素后面的兄弟元素。通常用于在文档的根节点之后设置处理指令或注释。注意,当在根级别添加时,tail文本将被自动丢弃。
def addprevious(self, element) -> typing.Any将元素直接添加为该元素前面的兄弟元素。
def append(self, element) -> typing.Any将子元素添加到该元素的末尾
def clear(self, keep_tail) -> typing.Any重置一个元素。这个函数删除所有子元素,清除所有属性,并将文本和尾部属性设置为None
def cssselect(self, expr) -> typing.Any在这个元素及其子元素上运行CSS表达式
def extend(self, elements) -> typing.Any通过iterable中的元素扩展当前子元素
def insert(self, index, element) -> typing.Any插入元素节点
def remove(self, element) -> typing.Any
def replace(self, old_element, new_element) -> typing.Any
def set(self, key, value) -> typing.Any
获取节点信息def tail(self) -> typing.Any【@property】获取tail
def text(self) -> typing.Any【@property】获取text文字内容
def xpath(self, _path, **_variables) -> typing.Any【@property】返回当前元素的所有属性
def xpath(self, _path, **_variables) -> typing.Any【@property】元素的基本URI (xml:base或HTML基本URL)
def nsmap(self) -> typing.Any【@property】
def prefix(self) -> typing.Any【@property】
def sourceline(self) -> typing.Any【@property】
def tag(self) -> typing.Any【@property】
查找节点def xpath(self, _path, **_variables) -> typing.Any该方法使用xpath语法,最常用
def find(self, path, namespaces) -> typing.Any获取当前节点下第一个匹配节点
def findall(self, path, namespaces) -> typing.Any获取当前节点下所有匹配元素
def findtext(self, path, default, namespaces) -> typing.Any获取某个节点下的文本节点
def getchildren(self) -> typing.Any获取当前节点下的所有子节点
def getiterator(self, tag, *tags) -> typing.Any获取当前节点所有子节点,迭代器
def getnext(self) -> typing.Any获取下一个节点
def getparent(self) -> typing.Any获取父节点
def getprevious(self) -> typing.Any返回该元素的前一个兄弟元素或None
def getroottree(self) -> typing.Any为文档的根节点返回一个ElementTree
def iter(self, tag, *tags) -> typing.Any查找元素,返回迭代器
def iterancestors(self, tag, *tags) -> typing.Any
def iterchildren(self, tag, *tags) -> typing.Any
def iterdescendants(self, tag, *tags) -> typing.Any
def iterfind(self, path, namespaces) -> typing.Any
def itersiblings(self, tag, *tags) -> typing.Any
def itertext(self, tag, *tags) -> typing.Any
查找节点属性def items(self) -> typing.Any获取当前节点所有属性
def keys(self) -> typing.Any
def get(self, key, default) -> typing.Any
def index(self, child, start, stop) -> typing.Any
def values(self) -> typing.Any

4.源码Demo

# -*- coding:utf-8 -*-
import os, requests

from lxml import etree, html
from lxml.etree import  _Element

current_workspace = os.path.join(os.path.dirname(os.path.abspath(__file__)))

url = 'https://so.gushiwen.cn/mingjus/'
#添加请求头
headers = {
    'User-Agent':'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36 QIHU 360SE'
}
reponse_obj = requests.get(url, headers=headers)
ele_html = etree.HTML(reponse_obj.text)
print(ele_html)
print('------------------------^v^-^v^-----------------^v^-^v^-------------------------')

# 可以直接转换字符串为ElemenDOM
def init_html_from_string():
    with open(os.path.join(current_workspace, 'input/demo.html'), 'r', encoding='utf-8') as f:
        html_str = f.read()
    print(html_str)
    html_obj = etree.HTML(html_str) #return <class 'lxml.etree._Element'>
    print(type(html_obj))

    html_obj = html.fromstring(html_str) # return <class 'lxml.html.HtmlElement'>
    print(type(html_obj))
    print(html_str)

#xpath(节点路径)来查找节点
#重点介绍xpath语法
def find_node_by_xpath(ele):
    #1、nodename:根据节点名称查询
    ele_list_by_nodename = ele.xpath('head')#查询当前节点下的所有head名称节点
    print(ele_list_by_nodename)

    #2、'/':根节点开始查询
    ele_list_by_rootNode = ele.xpath('/html/body') #查询当前节点下,/html/body所有节点
    print(ele_list_by_rootNode)

    #3、'//': 查询所有节点,从root节点开始
    ele_lis_by_every_node = ele.xpath('//div') #查询当前节点下所有div节点
    print(ele_lis_by_every_node)

    #4、'.': 代表当前节点
    ele_list_by_current_node = ele.xpath('.') #返回当前节点
    print(ele_list_by_current_node)

    #5、'..':代表父节点
    ele_list_by_father_node = ele.xpath('..') # 返回当前节点的父节点
    print(ele_list_by_father_node)

    #6、'@':返回某个属性的节点
    ele_list_by_attribute_node = ele.xpath('//div[@class="cont"]') #返回所有class=cont属性的div节点
    print(ele_list_by_attribute_node)

    #7、'*':返回所有节点;'@*':返回所有属性的节点;'node()':返回所有节点
    ele_list_by_wildcard_node = ele.xpath('*') #返回当前节点下所有子节点
    print(ele_list_by_wildcard_node)
    print('------------------------------------===---------------------------------------')
    ele_list_by_wildcard_attr_node = ele.xpath('//div[@*]') #返回所有具有属性的div节点
    print(ele_list_by_wildcard_attr_node)
    print('----------------------------------****-----------------------------------------')
    ele_list_by_wildcard_all_nodes = ele.xpath('node()') #返回当前节点下的所有节点,包括换行符号
    print(ele_list_by_wildcard_all_nodes)

    #综合应用:可以指定节点数字,制定对应的节点元素index
    print('*******************************************************************************')
    ele_list = ele.xpath('//*[@id="html"]/body/div[2]/div[1]/div[2]/*')
    print(ele_list)
    for e in ele_list:
        a_list = e.xpath('./a')
        if len(a_list) == 2:
            print(a_list[0].text, '-- -- -- --', a_list[1].text)
        else:
            print('NA NA  NA NA!')
            pass

    #综合应用 
    print('*******************************************************************************')
    ele_list_other = ele.xpath('//*[@id="html"]/body/div[2]/div[1]/div[2]/div/a')
    print(ele_list_other)
    for e in ele_list_other:
        print(e.text)

# 查找节点
def find_node_by_other_api(ele):
    #cssselect(self, expr) -> typing.Any
    # ele_div = ele.xpath('//*[@id="html"]/body/div[2]/div[1]/div[2]')[0]
    # print(ele_div)
    # ele_by_class = ele_div.cssselect('div.sons div.cont a') #'cssselect does not seem to be installed. '
    # print(ele_by_class)

    print('*******************************************************************************************')
    sons_ele = ele.xpath('//*[@id="html"]/body/div[2]/div[1]/div[2]/div[1]')[0]
    print(sons_ele)

    #find(self, path, namespaces) -> typing.Any
    a_by_find = sons_ele.find('a') #获取当前节点下第一个匹配节点
    print(a_by_find.text)

    #findall(self, path, namespaces) -> typing.Any
    a_by_findall = sons_ele.findall('a') #获取当前节点下所有匹配元素
    print(a_by_findall[0].text, '--------', a_by_findall[1].text)

    #findtext(self, path, default, namespaces) -> typing.Any
    a_by_findtext = sons_ele.findtext('a') #获取某个节点下的文本节点
    print(a_by_findtext)

    #getchildren(self) -> typing.Any
    a_by_getchildren = sons_ele.getchildren() #获取当前节点下的所有子节点
    print(a_by_getchildren)

    #getiterator(self, tag, *tags) -> typing.Any
    print('^^^^^^^^^^^^^^vvvvvv^^^^^^^^^^^^^^^^^^^^^^vvvvvv^^^^^^^^^^^^^vvvvv^^^^^^^^^^^^^^^^^^^^^^^^^^^^^')
    a_by_getiterator = sons_ele.getiterator() #获取当前节点所有子节点,迭代器
    print(a_by_getiterator)
    for e in a_by_getiterator:
        print(e)

    #getnext(self) -> typing.Any
    print('-------------------------------------1--------------------------------------------------')
    a_by_getnext = sons_ele.getnext() #获取下一个节点
    print(a_by_getnext)

    #getparent(self) -> typing.Any
    print('--------------------------------------2-------------------------------------------------')
    a_by_getparent = sons_ele.getparent() #获取父节点
    print(a_by_getparent)

    #getprevious(self) -> typing.Any
    print('---------------------------------------3------------------------------------------------')
    a_by_getprevious = sons_ele.getprevious() #返回该元素的前一个兄弟元素或None
    print(a_by_getprevious)

    #getroottree(self) -> typing.Any
    print('---------------------------------------4------------------------------------------------')
    a_by_getroottree = sons_ele.getroottree() #为文档的根节点返回一个ElementTree
    print(a_by_getroottree)

    #items(self) -> typing.Any
    print('---------------------------------------5------------------------------------------------')
    a_by_items = sons_ele.items() #获取当前节点所有属性
    print(a_by_items)

    #attrib(self) -> typing.Any 、@property
    print('---------------------------------------6------------------------------------------------')
    a_by_attrib = sons_ele.attrib #获取当前节点所有属性
    print(a_by_attrib)

    #get(self, key, default)
    print('---------------------------------------6------------------------------------------------')
    a_by_items = sons_ele.items() #获取当前节点所有属性
    print(a_by_items)

    #keys(self) -> typing.Any
    print('---------------------------------------7------------------------------------------------')
    a_by_keys = sons_ele.keys() #返回当前节点元素所有属性名称
    print(a_by_keys)

    #get(self, key, default) -> typing.Any
    print('---------------------------------------8------------------------------------------------')
    a_by_get = sons_ele.get('class') #返回当前节点 class属性值
    print(a_by_get)

    #iter(self, tag, *tags) -> typing.Any
    print('---------------------------------------9------------------------------------------------')
    a_by_iter = sons_ele.iter('a')
    print(a_by_iter)
    for e in a_by_iter:
        print(e)
    
    #clear(self, keep_tail) -> typing.Any
    sons_ele.clear() #清除当前初始化的节点

    #iterancestors(self, tag, *tags) -> typing.Any
    print('---------------------------------------10------------------------------------------------')
    a_by_iterancestors = sons_ele.iter('a')
    print(a_by_iterancestors)
    for e in a_by_iterancestors:
        print(e)  

    #........


if __name__ == '__main__':
    #init_html_from_string()
    find_node_by_xpath(ele_html) #通过xpath语法来查询对应的节点
    # find_node_by_other_api(ele_html) #通过其他api进行查询

3.在爬虫中综合应用

在爬虫处理HTML对象的时候,建议使用xpath,快捷,方便;当然针对获取相对关系的一些节点,可以使用def getchildren(self),def getnext(self)等提升性能。

# -*- coding:utf-8 -*-
import sys, io
# 编码问题终极解决办法
sys.stdout = io.TextIOWrapper(sys.stdout.buffer,encoding='gb18030') 

import requests, os, re
from lxml import etree, html
from requests.models import Response


#该py文件:处理urlib or requests模块返回的response,提取有用的信息

current_workspace = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'output')

#return response
def download_html_page():
    url = "https://so.gushiwen.cn/mingjus/"
    response_obj = requests.get(url, timeout=1) # <class 'requests.models.Response'>
    return response_obj

#re: 通过正则表达式的方式获取有用信息
# 优点,快速匹配数据
# 缺点:正则表达式难写+正则结构反复变化
def parse_response_by_re(response_page):
    u_re = '''<div class="cont" style.*
<a.*</a>
<span.*</span><a.*</a>
</div>'''
    regex = re.compile(u_re)

    page_content = response_page.content.decode('utf-8')
    # with open(os.path.join(current_workspace, 'deal_response.txt'), 'r', errors='ignore', encoding='UTF-8') as f:
    #     page_content = f.read()
    #print(page_content)

    matched_page_content_list = re.findall(regex, page_content)
    # print(matched_page_content_list)
    for line in matched_page_content_list:
        print("**deal %s**" %line)
        verse_re = '''<a.*>+?(.*)</a>''' #漂亮的正则表达式,要注意非贪婪匹配? 一定加载表示“次数”的后面
        verse_regex = re.compile(verse_re)
        verse_list = re.findall(verse_re, line) #['十年寒窗无人问,一举成名天下知。', '《增广贤文·上集》']
        print(verse_list)

# xpath路径进行获取:本质是将下载的html解析为树形结构,在根据xpath路径选择所需要的解析信息
def parse_response_by_lxml(response_page):
    # html_dom = etree.HTML(response_page.content)#return <class 'lxml.etree._Element'>
    html_dom = html.fromstring(response_page.content) ## return <class 'lxml.html.HtmlElement'>
    print(type(html_dom)) #如上两个基类一样。
    print(html_dom)
    print(html_dom.clear())
    print(html_dom)
    verse = html_dom.xpath('//*[@id="html"]/body/div[2]/div[1]/div[2]')# return <class 'lxml.etree._Element'>
    print(verse)
    for root_i in verse:
        print(root_i.attrib)
        all_div_list = root_i.findall('div')
        for div in all_div_list:
            all_a_list = div.findall('a')
            print(all_a_list)
            for a in all_a_list:
                print(a.text) #获取最终的结果

    
if __name__ == '__main__':
    # 下载网页
    reponse_page = download_html_page()

    #通过正则表达式获取有用信息
    # parse_response_by_re(reponse_page)

    #通过 xpath 获取对应的信息
    parse_response_by_lxml(reponse_page)

总结

提示:这里对文章进行总结:
例如:以上就是今天要讲的内容,本文仅仅简单介绍了pandas的使用,而pandas提供了大量能使我们快速便捷地处理数据的函数和方法。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值