前言
最近在处理网络爬虫下载的网页,使用到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提供了大量能使我们快速便捷地处理数据的函数和方法。