- 有效信息1
- 有效信息2
- 有效信息3
- 无效信息1
- 无效信息2
- 无效信息3
From:http://cuiqingcai.com/2621.html
The lxml.etree Tutorial :https://lxml.de/tutorial.htmlpython3 解析 xml:https://www.cnblogs.com/deadwood-2016/p/8116863.html
微软文档: XPath 语法 和 XPath 函数
W3school Xpath 教程:http://www.w3school.com.cn/xpath/
Xpath 菜鸟教程:http://www.runoob.com/xpath/xpath-tutorial.html
简书:Xpath高级用法:https://www.jianshu.com/p/1575db75670f
30个示例手把手教你学会Xpath高级用法:https://www.sohu.com/a/211716225_236714
了解XPath常用术语和表达式解析 十分钟轻松入门:http://www.bazhuayu.com/blog/2014091
XPath 即为 XML 路径语言,它是一种用来确定 XML(标准通用标记语言的子集)文档中某部分位置的语言。
XPath 基于 XML 的树状结构,提供在数据结构树中找寻节点的能力。 XPath 同样也支持HTML。
XPath 是一门小型的查询语言。
python 中 lxml库 使用的是 Xpath 语法,是效率比较高的解析方法。
lxml 用法源自 lxml python 官方文档:http://lxml.de/index.html
XPath 语法参考 w3school:http://www.w3school.com.cn/xpath/index.asp
pip3 install lxml
step1: 安装 lxml 库
step2: from lxml import etree # etree全称:ElementTree 元素树
step3: selector = etree.HTML(网页源代码)
step4: selector.xpath(一段神奇的符号)
lxml 使用 Xpath 使用示例:
#!/usr/bin/python3
# -*- coding: utf-8 -*-
# @Author :
# @File : test.py
# @Software : PyCharm
# @description : XXX
from lxml import etree
html = '''
- 有效信息1
- 有效信息2
- 有效信息3
- 无效信息1
- 无效信息2
- 无效信息3
'''
def test():
selector = etree.HTML(html)
# 提取 li 中的有效信息123
content = selector.xpath('//ul[@id="useful"]/li/text()')
for each in content:
print(each)
# 提取 a 中的属性
link = selector.xpath('//a/@href')
for each in link:
print(each)
title = selector.xpath('//a/@title')
for each in title:
print(each)
if __name__ == '__main__':
test()
lxml 使用 CSS 选择器 使用 示例 1:
#!/usr/bin/python3
# -*- coding: utf-8 -*-
# @Author :
# @File : test_2.py
# @Software : PyCharm
# @description : XXX
import lxml.html
from urllib.request import urlopen
def scrape(html):
tree = lxml.html.fromstring(html)
td = tree.cssselect('tr#places_neighbours__row > td.w2p_fw')[0]
area = td.text_content()
return area
if __name__ == '__main__':
r_html = urlopen('http://example.webscraping.com/view/United-Kingdom-239').read()
print(scrape(r_html))
lxml 使用 CSS 选择器 使用 示例2 :
# -*- coding: utf-8 -*-
import csv
import re
import urlparse
import lxml.html
from link_crawler import link_crawler
FIELDS = ('area', 'population', 'iso', 'country', 'capital', 'continent',
'tld', 'currency_code', 'currency_name', 'phone', 'postal_code_format',
'postal_code_regex', 'languages', 'neighbours')
def scrape_callback(url, html):
if re.search('/view/', url):
tree = lxml.html.fromstring(html)
row = [tree.cssselect('table > tr#places_{}__row > td.w2p_fw'.format(field))[0].text_content() for field in FIELDS]
print url, row
if __name__ == '__main__':
link_crawler('http://example.webscraping.com/', '/(index|view)', scrape_callback=scrape_callback)
link_crawler.py
import re
import urlparse
import urllib2
import time
from datetime import datetime
import robotparser
import Queue
def link_crawler(seed_url, link_regex=None, delay=5, max_depth=-1, max_urls=-1, headers=None, user_agent='wswp', proxy=None, num_retries=1, scrape_callback=None):
"""Crawl from the given seed URL following links matched by link_regex
"""
# the queue of URL's that still need to be crawled
crawl_queue = [seed_url]
# the URL's that have been seen and at what depth
seen = {seed_url: 0}
# track how many URL's have been downloaded
num_urls = 0
rp = get_robots(seed_url)
throttle = Throttle(delay)
headers = headers or {}
if user_agent:
headers['User-agent'] = user_agent
while crawl_queue:
url = crawl_queue.pop()
depth = seen[url]
# check url passes robots.txt restrictions
if rp.can_fetch(user_agent, url):
throttle.wait(url)
html = download(url, headers, proxy=proxy, num_retries=num_retries)
links = []
if scrape_callback:
links.extend(scrape_callback(url, html) or [])
if depth != max_depth:
# can still crawl further
if link_regex:
# filter for links matching our regular expression
links.extend(link for link in get_links(html) if re.match(link_regex, link))
for link in links:
link = normalize(seed_url, link)
# check whether already crawled this link
if link not in seen:
seen[link] = depth + 1
# check link is within same domain
if same_domain(seed_url, link):
# success! add this new link to queue
crawl_queue.append(link)
# check whether have reached downloaded maximum
num_urls += 1
if num_urls == max_urls:
break
else:
print 'Blocked by robots.txt:', url
class Throttle:
"""Throttle downloading by sleeping between requests to same domain
"""
def __init__(self, delay):
# amount of delay between downloads for each domain
self.delay = delay
# timestamp of when a domain was last accessed
self.domains = {}
def wait(self, url):
"""Delay if have accessed this domain recently
"""
domain = urlparse.urlsplit(url).netloc
last_accessed = self.domains.get(domain)
if self.delay > 0 and last_accessed is not None:
sleep_secs = self.delay - (datetime.now() - last_accessed).seconds
if sleep_secs > 0:
time.sleep(sleep_secs)
self.domains[domain] = datetime.now()
def download(url, headers, proxy, num_retries, data=None):
print 'Downloading:', url
request = urllib2.Request(url, data, headers)
opener = urllib2.build_opener()
if proxy:
proxy_params = {urlparse.urlparse(url).scheme: proxy}
opener.add_handler(urllib2.ProxyHandler(proxy_params))
try:
response = opener.open(request)
html = response.read()
code = response.code
except urllib2.URLError as e:
print 'Download error:', e.reason
html = ''
if hasattr(e, 'code'):
code = e.code
if num_retries > 0 and 500 <= code < 600:
# retry 5XX HTTP errors
html = download(url, headers, proxy, num_retries-1, data)
else:
code = None
return html
def normalize(seed_url, link):
"""Normalize this URL by removing hash and adding domain
"""
link, _ = urlparse.urldefrag(link) # remove hash to avoid duplicates
return urlparse.urljoin(seed_url, link)
def same_domain(url1, url2):
"""Return True if both URL's belong to same domain
"""
return urlparse.urlparse(url1).netloc == urlparse.urlparse(url2).netloc
def get_robots(url):
"""Initialize robots parser for this domain
"""
rp = robotparser.RobotFileParser()
rp.set_url(urlparse.urljoin(url, '/robots.txt'))
rp.read()
return rp
def get_links(html):
"""Return a list of links from html
"""
# a regular expression to extract all links from the webpage
webpage_regex = re.compile(']+href=["\'](.*?)["\']', re.IGNORECASE)
# list of all links from the webpage
return webpage_regex.findall(html)
if __name__ == '__main__':
link_crawler('http://example.webscraping.com', '/(index|view)', delay=0, num_retries=1, user_agent='BadCrawler')
link_crawler('http://example.webscraping.com', '/(index|view)', delay=0, num_retries=1, max_depth=1, user_agent='GoodCrawler')
来源:https://www.cnblogs.com/deadwood-2016/p/8116863.html
Python 使用 XPath 解析 XML文档 :http://www.jingfengshuo.com/archives/1414.html
在 XML 解析方面,Python 贯彻了自己“开箱即用”(batteries included)的原则。在自带的标准库中,Python提供了大量可以用于处理XML语言的包和工具,数量之多,甚至让Python编程新手无从选择。
本文将介绍深入解读利用Python语言解析XML文件的几种方式,并以笔者推荐使用的ElementTree模块为例,演示具体使用方法和场景。
XML是可扩展标记语言(Extensible Markup Language)的缩写,其中的 标记(markup)是关键部分。您可以创建内容,然后使用限定标记标记它,从而使每个单词、短语或块成为可识别、可分类的信息。
标记语言从早期的私有公司和政府制定形式逐渐演变成标准通用标记语言(Standard Generalized Markup Language,SGML)、超文本标记语言(Hypertext Markup Language,HTML),并且最终演变成 XML。XML有以下几个特点。
目前,XML在Web中起到的作用不会亚于一直作为Web基石的HTML。 XML无所不在。XML是各种应用程序之间进行数据传输的最常用的工具,并且在信息存储和描述领域变得越来越流行。因此,学会如何解析XML文件,对于Web开发来说是十分重要的。
Python的标准库中,提供了6种可以用于处理XML的包。
xml.dom
xml.dom实现的是W3C制定的DOM API。如果你习惯于使用DOM API或者有人要求这这样做,可以使用这个包。不过要注意,在这个包中,还提供了几个不同的模块,各自的性能有所区别。
DOM解析器在任何处理开始之前,必须把基于XML文件生成的树状数据放在内存,所以DOM解析器的内存使用量完全根据输入资料的大小。
xml.dom.minidom
xml.dom.minidom是DOM API的极简化实现,比完整版的DOM要简单的多,而且这个包也小的多。那些不熟悉DOM的朋友,应该考虑使用xml.etree.ElementTree模块。据lxml的作者评价,这个模块使用起来并不方便,效率也不高,而且还容易出现问题。
xml.dom.pulldom
与其他模块不同,xml.dom.pulldom模块提供的是一个“pull解析器”,其背后的基本概念指的是从XML流中pull事件,然后进行处理。虽然与SAX一样采用事件驱动模型(event-driven processing model),但是不同的是,使用pull解析器时,使用者需要明确地从XML流中pull事件,并对这些事件遍历处理,直到处理完成或者出现错误。
xml.sax
xml.sax模块实现的是SAX API,这个模块牺牲了便捷性来换取速度和内存占用。SAX是Simple API for XML的缩写,它并不是由W3C官方所提出的标准。它是事件驱动的,并不需要一次性读入整个文档,而文档的读入过程也就是SAX的解析过程。所谓事件驱动,是指一种基于回调(callback)机制的程序运行方法。
xml.parser.expat
xml.parser.expat提供了对C语言编写的expat解析器的一个直接的、底层API接口。expat接口与SAX类似,也是基于事件回调机制,但是这个接口并不是标准化的,只适用于expat库。
expat是一个面向流的解析器。您注册的解析器回调(或handler)功能,然后开始搜索它的文档。当解析器识别该文件的指定的位置,它会调用该部分相应的处理程序(如果您已经注册的一个)。该文件被输送到解析器,会被分割成多个片断,并分段装到内存中。因此expat可以解析那些巨大的文件。
xml.etree.ElementTree(以下简称ET)
xml.etree.ElementTree模块提供了一个轻量级、Pythonic的API,同时还有一个高效的C语言实现,即xml.etree.cElementTree。与DOM相比,ET的速度更快,API使用更直接、方便。与SAX相比,ET.iterparse函数同样提供了按需解析的功能,不会一次性在内存中读入整个文档。ET的性能与SAX模块大致相仿,但是它的API更加高层次,用户使用起来更加便捷。
笔者建议,在使用Python进行XML解析时,首选使用ET模块,除非你有其他特别的需求,可能需要另外的模块来满足。
下面,我们以ElementTree模块为例,介绍在Python中如何解析lxml。
Python标准库中,提供了ET的两种实现。一个是纯Python实现的xml.etree.ElementTree,另一个是速度更快的C语言实现xml.etree.cElementTree。请记住始终使用C语言实现,因为它的速度要快很多,而且内存消耗也要少很多。如果你所使用的Python版本中没有cElementTree所需的加速模块,你可以这样导入模块:
try:
import xml.etree.cElementTree as ET
except ImportError:
import xml.etree.ElementTree as ET
如果某个API存在不同的实现,上面是常见的导入方式。当然,很可能你直接导入第一个模块时,并不会出现问题。请注意,自Python 3.3之后,就不用采用上面的导入方法,因为ElemenTree模块会自动优先使用C加速器,如果不存在C实现,则会使用Python实现。因此,使用Python 3.3+的朋友,只需要import xml.etree.ElementTree即可。
1、将XML文档解析为树(tree)
我们先从基础讲起。XML是一种结构化、层级化的数据格式,最适合体现XML的数据结构就是树。ET提供了两个对象:ElementTree将整个XML文档转化为树,Element则代表着树上的单个节点。对整个XML文档的交互(读取,写入,查找需要的元素),一般是在ElementTree层面进行的。对单个XML元素及其子元素,则是在Element层面进行的。下面我们举例介绍主要使用方法。
我们使用下面的XML文档,作为演示数据:
text,source
xml,sgml
接下来,我们加载这个文档,并进行解析:
>>> import xml.etree.ElementTree as ET
>>> tree = ET.ElementTree(file='doc1.xml')
然后,我们获取根元素(root element):
>>> tree.getroot()
正如之前所讲的,根元素(root)是一个Element对象。我们看看根元素都有哪些属性:
>>> root = tree.getroot()
>>> root.tag, root.attrib
('doc', {})
没错,根元素并没有属性。与其他Element对象一样,根元素也具备遍历其直接子元素的接口:
>>> for child_of_root in root:
... print child_of_root.tag, child_of_root.attrib
...
branch {'hash': '1cdf045c', 'name': 'codingpy.com'}
branch {'hash': 'f200013e', 'name': 'release01'}
branch {'name': 'invalid'}
我们还可以通过索引值来访问特定的子元素:
>>> root[0].tag, root[0].text
('branch', '\n text,source\n ')
2、查找需要的元素
从上面的示例中,可以明显发现我们能够通过简单的递归方法(对每一个元素,递归式访问其所有子元素)获取树中的所有元素。但是,由于这是十分常见的工作,ET提供了一些简便的实现方法。
Element对象有一个iter方法,可以对某个元素对象之下所有的子元素进行深度优先遍历(DFS)。ElementTree对象同样也有这个方法。下面是查找XML文档中所有元素的最简单方法:
>>> for elem in tree.iter():
... print elem.tag, elem.attrib
...
doc {}
branch {'hash': '1cdf045c', 'name': 'codingpy.com'}
branch {'hash': 'f200013e', 'name': 'release01'}
sub-branch {'name': 'subrelease01'}
branch {'name': 'invalid'}
在此基础上,我们可以对树进行任意遍历——遍历所有元素,查找出自己感兴趣的属性。但是ET可以让这个工作更加简便、快捷。iter方法可以接受tag名称,然后遍历所有具备所提供tag的元素:
>>> for elem in tree.iter(tag='branch'):
... print elem.tag, elem.attrib
...
branch {'hash': '1cdf045c', 'name': 'codingpy.com'}
branch {'hash': 'f200013e', 'name': 'release01'}
branch {'name': 'invalid'}
3、支持通过 XPath 查找元素
使用XPath查找感兴趣的元素,更加方便。Element对象中有一些find方法可以接受Xpath路径作为参数,find方法会返回第一个匹配的子元素,findall以列表的形式返回所有匹配的子元素, iterfind则返回一个所有匹配元素的迭代器(iterator)。ElementTree对象也具备这些方法,相应地它的查找是从根节点开始的。
下面是一个使用XPath查找元素的示例:
>>> for elem in tree.iterfind('branch/sub-branch'):
... print elem.tag, elem.attrib
...
sub-branch {'name': 'subrelease01'}
上面的代码返回了branch元素之下所有tag为sub-branch的元素。接下来查找所有具备某个name属性的branch元素:
>>> for elem in tree.iterfind('branch[@name="release01"]'):
... print elem.tag, elem.attrib
...
branch {'hash': 'f200013e', 'name': 'release01'}
4、构建 XML 文档
利用ET,很容易就可以完成XML文档构建,并写入保存为文件。ElementTree对象的write方法就可以实现这个需求。
一般来说,有两种主要使用场景。一是你先读取一个XML文档,进行修改,然后再将修改写入文档,二是从头创建一个新XML文档。
修改文档的话,可以通过调整Element对象来实现。请看下面的例子:
>>> root = tree.getroot()
>>> del root[2]
>>> root[0].set('foo', 'bar')
>>> for subelem in root:
... print subelem.tag, subelem.attrib
...
branch {'foo': 'bar', 'hash': '1cdf045c', 'name': 'codingpy.com'}
branch {'hash': 'f200013e', 'name': 'release01'}
在上面的代码中,我们删除了root元素的第三个子元素,为第一个子元素增加了新属性。这个树可以重新写入至文件中。最终的XML文档应该是下面这样的:
>>> import sys
>>> tree.write(sys.stdout)
text,source
xml,sgml
请注意,文档中元素的属性顺序与原文档不同。这是因为ET是以字典的形式保存属性的,而字典是一个无序的数据结构。当然,XML也不关注属性的顺序。
从头构建一个完整的文档也很容易。ET模块提供了一个SubElement工厂函数,让创建元素的过程变得很简单:
>>> a = ET.Element('elem')
>>> c = ET.SubElement(a, 'child1')
>>> c.text = "some text"
>>> d = ET.SubElement(a, 'child2')
>>> b = ET.Element('elem_b')
>>> root = ET.Element('root')
>>> root.extend((a, b))
>>> tree = ET.ElementTree(root)
>>> tree.write(sys.stdout)
some text
5、利用iterparse解析XML流
XML文档通常都会比较大,如何直接将文档读入内存的话,那么进行解析时就会出现问题。这也就是为什么不建议使用DOM,而是SAX API的理由之一。
我们上面谈到,ET可以将XML文档加载为保存在内存里的树(in-memory tree),然后再进行处理。但是在解析大文件时,这应该也会出现和DOM一样的内存消耗大的问题吧?没错,的确有这个问题。为了解决这个问题,ET提供了一个类似SAX的特殊工具——iterparse,可以循序地解析XML。
接下来,笔者为大家展示如何使用iterparse,并与标准的树解析方式进行对比。我们使用一个自动生成的XML文档,下面是该文档的开头部分:
-
United States
1
duteous nine eighteen
Creditcard
[...]
我们来统计一下文档中出现了多少个文本值为Zimbabwe的location元素。下面是使用ET.parse的标准方法:
tree = ET.parse(sys.argv[2])
count = 0
for elem in tree.iter(tag='location'):
if elem.text == 'Zimbabwe':
count += 1
print count
上面的代码会将全部元素载入内存,逐一解析。当解析一个约100MB的XML文档时,运行上面脚本的Python进程的内存使用峰值为约560MB,总运行时间问2.9秒。
请注意,我们其实不需要讲整个树加载到内存里。只要检测出文本为相应值得location元素即可。其他数据都可以废弃。这时,我们就可以用上iterparse方法了:
count = 0
for event, elem in ET.iterparse(sys.argv[2]):
if event == 'end':
if elem.tag == 'location' and elem.text == 'Zimbabwe':
count += 1
elem.clear() # 将元素废弃
print count
上面的for循环会遍历iterparse事件,首先检查事件是否为end,然后判断元素的tag是否为location,以及其文本值是否符合目标值。另外,调用elem.clear()非常关键:因为iterparse仍然会生成一个树,只是循序生成的而已。废弃掉不需要的元素,就相当于废弃了整个树,释放出系统分配的内存。
当利用上面这个脚本解析同一个文件时,内存使用峰值只有7MB,运行时间为2.5秒。速度提升的原因,是我们这里只在树被构建时,遍历一次。而使用parse的标准方法是先完成整个树的构建后,才再次遍历查找所需要的元素。
iterparse的性能与SAX相当,但是其API却更加有用:iterparse会循序地构建树;而利用SAX时,你还得自己完成树的构建工作。
#!/usr/bin/python3
# -*- coding: utf-8 -*-
# @Author :
# @File : test_1.py
# @Software : PyCharm
# @description : XXX
"""
Element是 XML处理的核心类,
Element对象可以直观的理解为 XML的节点,大部分 XML节点的处理都是围绕该类进行的。
这部分包括三个内容:节点的操作、节点属性的操作、节点内文本的操作。
"""
from lxml import etree
import lxml.html as HTML
# 1.创建element
root = etree.Element('root')
print(root, root.tag)
# 2.添加子节点
child1 = etree.SubElement(root, 'child1')
child2 = etree.SubElement(root, 'child2')
# 3.删除子节点
# root.remove(child2)
# 4.删除所有子节点
# root.clear()
# 5.以列表的方式操作子节点
print(len(root))
print(root.index(child1)) # 索引号
root.insert(0, etree.Element('child3')) # 按位置插入
root.append(etree.Element('child4')) # 尾部添加
# 6.获取父节点
print(child1.getparent().tag)
# print root[0].getparent().tag #用列表获取子节点,再获取父节点
'''以上都是节点操作'''
# 7.创建属性
# root.set('hello', 'dahu') #set(属性名,属性值)
# root.set('hi', 'qing')
# 8.获取属性
# print(root.get('hello')) #get方法
# print root.keys(),root.values(),root.items() #参考字典的操作
# print root.attrib #直接拿到属性存放的字典,节点的attrib,就是该节点的属性
'''以上是属性的操作'''
# 9.text和tail属性
# root.text = 'Hello, World!'
# print root.text
# 10.test,tail 和 text 的结合
html = etree.Element('html')
html.text = 'html.text'
body = etree.SubElement(html, 'body')
body.text = 'wo ai ni'
child = etree.SubElement(body, 'child')
child.text = 'child.text' # 一般情况下,如果一个节点的text没有内容,就只有>符号,如果有内容,才会<>,>都有
child.tail = 'tails' # tail是在标签后面追加文本
print(etree.tostring(html))
# print(etree.tostring(html, method='text')) # 只输出text和tail这种文本文档,输出的内容连在一起,不实用
# 11.Xpath方式
# print(html.xpath('string()')) #这个和上面的方法一样,只返回文本的text和tail
print(html.xpath('//text()')) # 这个比较好,按各个文本值存放在列表里面
tt = html.xpath('//text()')
print(tt[0].getparent().tag) # 这个可以,首先我可以找到存放每个节点的text的列表,然后我再根据text找相应的节点
# for i in tt:
# print i,i.getparent().tag,'\t',
# 12.判断文本类型
print(tt[0].is_text, tt[-1].is_tail) # 判断是普通text文本,还是tail文本
'''以上都是文本的操作'''
# 13.字符串解析,fromstring方式
xml_data = 'html.textwo ai nichild.text tails'
root1 = etree.fromstring(xml_data) # fromstring,字面意思,直接来源字符串
# print root1.tag
# print etree.tostring(root1)
# 14.xml方式
root2 = etree.XML(xml_data) # 和fromstring基本一样,
print(etree.tostring(root2))
# 15.文件类型解析
'''
- a file name/path
- a file object
- a file-like object
- a URL using the HTTP or FTP protocol
'''
tree = etree.parse('text.html') # 文件解析成元素树
root3 = tree.getroot() # 获取元素树的根节点
print(etree.tostring(root3, pretty_print=True))
parser = etree.XMLParser(remove_blank_text=True) # 去除xml文件里的空行
root = etree.XML(" ", parser)
print(etree.tostring(root))
# 16.html方式
xml_data1 = 'data '
root4 = etree.HTML(xml_data1)
print(etree.tostring(root4)) # HTML方法,如果没有和标签,会自动补上
# 注意,如果是需要补全的html格式:这样处理哦
with open("quotes-1.html", 'r') as f:
a = HTML.document_fromstring(f.read().decode("utf-8"))
for i in a.xpath('//div[@class="quote"]/span[@class="text"]/text()'):
print(i)
# 17.输出内容,输出xml格式
print(etree.tostring(root))
print(etree.tostring(root, xml_declaration=True, pretty_print=True, encoding='utf-8')) # 指定xml声明和编码
'''以上是文件IO操作'''
# 18.findall方法
root = etree.XML("aText ")
print(root.findall('a')[0].text) # findall操作返回列表
print(root.find('.//a').text) # find操作就相当与找到了这个元素节点,返回匹配到的第一个元素
print(root.find('a').text)
print([b.text for b in root.findall('.//a')]) # 配合列表解析,相当帅气!
print(root.findall('.//a[@x]')[0].tag) # 根据属性查询
'''以上是搜索和定位操作'''
print(etree.iselement(root))
print(root[0] is root[1].getprevious()) # 子节点之间的顺序
print(root[1] is root[0].getnext())
'''其他技能'''
# 遍历元素数
root = etree.Element("root")
etree.SubElement(root, "child").text = "Child 1"
etree.SubElement(root, "child").text = "Child 2"
etree.SubElement(root, "another").text = "Child 3"
etree.SubElement(root[0], "childson").text = "son 1"
# for i in root.iter(): #深度遍历
# for i in root.iter('child'): #只迭代目标值
# print i.tag,i.text
# print etree.tostring(root,pretty_print=True)
简单的创建和遍历
from lxml import etree
# 创建
root = etree.Element('root')
# 添加子元素,并为子节点添加属性
root.append(etree.Element('child',interesting='sss'))
# 另一种添加子元素的方法
body = etree.SubElement(root,'body')
body.text = 'TEXT' # 设置值
body.set('class','dd') # 设置属性
//
# 输出整个节点
print(etree.tostring(root, encoding='UTF-8', pretty_print=True))
//
//
# 创建,添加子节点、文本、注释
root = etree.Element('root')
etree.SubElement(root, 'child').text = 'Child 1'
etree.SubElement(root, 'child').text = 'Child 2'
etree.SubElement(root, 'child').text = 'Child 3'
root.append(etree.Entity('#234'))
root.append(etree.Comment('some comment')) # 添加注释
# 为第三个节点添加一个br
br = etree.SubElement(root.getchildren()[2],'br')
br.tail = 'TAIL'
for element in root.iter(): # 也可以指定只遍历是Element的,root.iter(tag=etree.Element)
if isinstance(element.tag, str):
print('%s - %s' % (element.tag, element.text))
else:
print('SPECIAL: %s - %s' % (element, element.text))
对HTML/XML的解析
# 先导入相关模块
from lxml import etree
from io import StringIO, BytesIO
# 对html具有修复标签补全的功能
broken_html = 'test page title'
parser = etree.HTMLParser()
tree = etree.parse(StringIO(broken_html), parser) # 或者直接使用 html = etree.HTML(broken_html)
print(etree.tostring(tree, pretty_print=True, method="html"))
#
#
#用xpath获取h1
h1 = tree.xpath('//h1') # 返回的是一个数组
# 获取第一个的tag
print(h1[0].tag)
# 获取第一个的class属性
print(h1[0].get('class'))
# 获取第一个的文本内容
print(h1[0].text)
# 获取所有的属性的key,value的列表
print(h1[0].keys(),h1[0].values())
杂项
python3.5 lxml用法
问题1:有一个XML文件,如何解析
问题2:解析后,如果查找、定位某个标签
问题3:定位后如何操作标签,比如访问属性、文本内容等
开始之前,首先是导入模块,该库常用的XML处理功能都在lxml.etree中
导入模块:from lxml import etree
Element类
Element是XML处理的核心类,Element对象可以直观的理解为XML的节点,大部分XML节点的处理都是围绕该类进行的。
这部分包括三个内容:节点的操作、节点属性的操作、节点内文本的操作。
节点操作
1、创建Element对象
直接使用Element方法,参数即节点名称。
root = etree.Element(‘root’)
print(root)
2、获取节点名称
使用tag属性,获取节点的名称。
print(root.tag)
root
3、输出XML内容
使用tostring方法输出XML内容(后文还会有补充介绍),参数为Element对象。
print(etree.tostring(root))
b’’
4、添加子节点
使用SubElement方法创建子节点,第一个参数为父节点(Element对象),第二个参数为子节点名称。
child1 = etree.SubElement(root, ‘child1’)
child2 = etree.SubElement(root, ‘child2’)
child3 = etree.SubElement(root, ‘child3’)
5、删除子节点
使用remove方法删除指定节点,参数为Element对象。clear方法清空所有节点。
root.remove(child1) # 删除指定子节点
print(etree.tostring(root))
b’’
root.clear() # 清除所有子节点
print(etree.tostring(root))
b’’
6、以列表的方式操作子节点
可以将Element对象的子节点视为列表进行各种操作:
child = root[0] # 下标访问
print(child.tag)
child1
print(len(root)) # 子节点数量
3
root.index(child2) # 获取索引号
1
for child in root: # 遍历
… print(child.tag)
child1
child2
child3
root.insert(0, etree.Element(‘child0’)) # 插入
start = root[:1] # 切片
end = root[-1:]
print(start[0].tag)
child0
print(end[0].tag)
child3
root.append( etree.Element(‘child4’) ) # 尾部添加
print(etree.tostring(root))
b’’
其实前面讲到的删除子节点的两个方法remove和clear也和列表相似。
7、获取父节点
使用getparent方法可以获取父节点。
print(child1.getparent().tag)
root
属性操作
属性是以key-value的方式存储的,就像字典一样。
1、创建属性
可以在创建Element对象时同步创建属性,第二个参数即为属性名和属性值:
root = etree.Element(‘root’, interesting=’totally’)
print(etree.tostring(root))
b’’
也可以使用set方法给已有的Element对象添加属性,两个参数分别为属性名和属性值:
root.set(‘hello’, ‘Huhu’)
print(etree.tostring(root))
b’’
2、获取属性
属性是以key-value的方式存储的,就像字典一样。直接看例子
get方法获得某一个属性值
print(root.get(‘interesting’))
totally
keys方法获取所有的属性名
sorted(root.keys())
[‘hello’, ‘interesting’]
items方法获取所有的键值对
for name, value in sorted(root.items()):
… print(‘%s = %r’ % (name, value))
hello = ‘Huhu’
interesting = ‘totally’
也可以用attrib属性一次拿到所有的属性及属性值存于字典中:
attributes = root.attrib
print(attributes)
{‘interesting’: ‘totally’, ‘hello’: ‘Huhu’}
attributes[‘good’] = ‘Bye’ # 字典的修改影响节点
print(root.get(‘good’))
Bye
文本操作
标签及标签的属性操作介绍完了,最后就剩下标签内的文本了。
可以使用text和tail属性、或XPath的方式来访问文本内容。
1、text 和 tail 属性
一般情况,可以用Element的text属性访问标签的文本。
root = etree.Element(‘root’)
root.text = ‘Hello, World!’
print(root.text)
Hello, World!
print(etree.tostring(root))
b’Hello, World!’
Element类提供了tail属性支持单一标签的文本获取。
html = etree.Element(‘html’)
body = etree.SubElement(html, ‘body’)
body.text = ‘Text’
print(etree.tostring(html))
b’Text’
br = etree.SubElement(body, ‘br’)
print(etree.tostring(html))
b’Text’
tail仅在该标签后面追加文本
br.tail = ‘Tail’
print(etree.tostring(br))
b’
Tail’
print(etree.tostring(html))
b’Text
Tail’
tostring方法增加method参数,过滤单一标签,输出全部文本
print(etree.tostring(html, method=’text’))
b’TextTail’
2、XPath方式
方式一:过滤单一标签,返回文本
print(html.xpath(‘string()’))
TextTail
方式二:返回列表,以单一标签为分隔
print(html.xpath(‘//text()’))
[‘Text’, ‘Tail’]
方法二获得的列表,每个元素都会带上它所属节点及文本类型信息,如下:
texts = html.xpath(‘//text()’))
print(texts[0])
Text
所属节点
parent = texts[0].getparent()
print(parent.tag)
body
print(texts[1], texts[1].getparent().tag)
Tail br
文本类型:是普通文本还是tail文本
print(texts[0].is_text)
True
print(texts[1].is_text)
False
print(texts[1].is_tail)
True
文件 解析 与 输出
回答问题1。
这部分讲述如何将XML文件解析为Element对象,以及如何将Element对象输出为XML文件。
1、文件解析
文件解析常用的有fromstring、XML 和 HTML 三个方法。接受的参数都是字符串。
xml_data = ‘data’
fromstring方法
root1 = etree.fromstring(xml_data)
print(root1.tag)
root
print(etree.tostring(root1))
b’data’
XML方法,与fromstring方法基本一样
root2 = etree.XML(xml_data)
print(root2.tag)
root
print(etree.tostring(root2))
b’data’
HTML方法,如果没有和标签,会自动补上
root3 = etree.HTML(xml_data)
print(root3.tag)
html
print(etree.tostring(root3))
b’data’
2、输出
输出其实就是前面一直在用的tostring方法了,这里补充xml_declaration和encoding两个参数,前者是XML声明,后者是指定编码。
root = etree.XML(‘‘)
print(etree.tostring(root))
b’’
XML声明
print(etree.tostring(root, xml_declaration=True))
b”
指定编码
print(etree.tostring(root, encoding=’iso-8859-1’))
b”
查找第一个b标签
print(root.find(‘b’))
None
print(root.find(‘a’).tag)
a
查找所有b标签,返回Element对象组成的列表
[ b.tag for b in root.findall(‘.//b’) ]
[‘b’, ‘b’]
根据属性查询
print(root.findall(‘.//a[@x]’)[0].tag)
a
print(root.findall(‘.//a[@y]’))
[]
XPath 是一门在 XML 文档中查找信息的语言。XPath 可用来在 XML 文档中对元素和属性进行遍历。XPath 是 W3C XSLT 标准的主要元素,并且 XQuery 和 XPointer 都构建于 XPath 表达之上。
XPath 常用规则
表达式 | 描述 |
nodename | 选取此节点的所有子节点 |
/ | 从当前节点选取直接子节点 |
// | 从当前节点选取子孙节点 |
. | 选取当前节点 |
.. | 选取当前节点的父节点 |
@ | 选取属性 |
* | 通配符,选择所有元素节点与元素名 |
@* | 选取所有属性 |
[@attrib] | 选取具有给定属性的所有元素 |
[@attrib='value'] | 选取给定属性具有给定值的所有元素 |
[tag] | 选取所有具有指定元素的直接子节点 |
[tag='text'] | 选取所有具有指定元素并且文本内容是text节点 |
读取 文本 解析节点 ( etree 会修复 HTML 文本节点 )
#!/usr/bin/python3
# -*- coding: utf-8 -*-
# @Author :
# @File : test.py
# @Software : PyCharm
# @description : XXX
from lxml import etree
text = '''
'''
html = etree.HTML(text) # 初始化生成一个XPath解析对象
result = etree.tostring(html, encoding='utf-8') # 解析对象输出代码
print(type(html))
print(type(result))
print(result.decode('utf-8'))
'''
执行结果:
'''
读取 HTML文件 进行解析
from lxml import etree
html = etree.parse('test.html', etree.HTMLParser()) # 指定解析器HTMLParser会根据文件修复HTML文件中缺失的如声明信息
result = etree.tostring(html) # 解析成字节
# result=etree.tostringlist(html) #解析成列表
print(type(html))
print(type(result))
print(result)
每个元素以及属性都有一个父。在下面的例子中,book 元素是 title、author、year 以及 price 元素的父:
Harry Potter
J K. Rowling
2005
29.99
元素节点可有零个、一个或多个子。在下面的例子中,title、author、year 以及 price 元素都是 book 元素的子:
Harry Potter
J K. Rowling
2005
29.99
拥有相同的父的节点。在下面的例子中,title、author、year 以及 price 元素都是同胞:
Harry Potter
J K. Rowling
2005
29.99
某节点的父、父的父,等等。在下面的例子中,title 元素的先辈是 book 元素和 bookstore 元素:
Harry Potter
J K. Rowling
2005
29.99
某个节点的子,子的子,等等。在下面的例子中,bookstore 的后代是 book、title、author、year 以及 price 元素:
Harry Potter
J K. Rowling
2005
29.99
XPath 使用路径表达式在 XML 文档中选取节点。节点是通过沿着路径或者 step 来选取的。
表达式 | 描述 |
nodename | 选取此节点的所有子节点 |
/ | 从当前节点选取直接子节点 |
// | 从当前节点选取子孙节点 |
. | 选取当前节点 |
.. | 选取当前节点的父节点 |
@ | 选取属性 |
* | 通配符,选择所有元素节点与元素名 |
@* | 选取所有属性 |
[@attrib] | 选取具有给定属性的所有元素 |
[@attrib='value'] | 选取给定属性具有给定值的所有元素 |
[tag] | 选取所有具有指定元素的直接子节点 |
[tag='text'] | 选取所有具有指定元素并且文本内容是text节点 |
实例
在下面的表格中,我们已列出了一些路径表达式以及表达式的结果:
路径表达式 | 结果 |
---|---|
bookstore | 选取 bookstore 元素的所有子节点。 |
/bookstore | 选取根元素 bookstore。注释:假如路径起始于正斜杠( / ),则此路径始终代表到某元素的绝对路径! |
bookstore/book | 选取属于 bookstore 的子元素的所有 book 元素。 |
//book | 选取所有 book 子元素,而不管它们在文档中的位置。 |
bookstore//book | 选择属于 bookstore 元素的后代的所有 book 元素,而不管它们位于 bookstore 之下的什么位置。 |
//@lang | 选取名为 lang 的所有属性。 |
谓语用来查找某个特定的节点或者包含某个指定的值的节点。谓语被嵌在方括号中。
实例
在下面的表格中,我们列出了带有谓语的一些路径表达式,以及表达式的结果:
路径表达式 | 结果 |
---|---|
/bookstore/book[1] | 选取属于 bookstore 子元素的第一个 book 元素。 |
/bookstore/book[last()] | 选取属于 bookstore 子元素的最后一个 book 元素。 |
/bookstore/book[last()-1] | 选取属于 bookstore 子元素的倒数第二个 book 元素。 |
/bookstore/book[position()<3] | 选取最前面的两个属于 bookstore 元素的子元素的 book 元素。 |
//title[@lang] | 选取所有拥有名为 lang 的属性的 title 元素。 |
//title[@lang=’eng’] | 选取所有 title 元素,且这些元素拥有值为 eng 的 lang 属性。 |
/bookstore/book[price>35.00] | 选取 bookstore 元素的所有 book 元素,且其中的 price 元素的值须大于 35.00。 |
/bookstore/book[price>35.00]/title | 选取 bookstore 元素中的 book 元素的所有 title 元素,且其中的 price 元素的值须大于 35.00。 |
XPath 通配符可用来选取未知的 XML 元素。
通配符 | 描述 |
---|---|
* | 匹配任何元素节点。 |
@* | 匹配任何属性节点。 |
node() | 匹配任何类型的节点。 |
实例
在下面的表格中,我们列出了一些路径表达式,以及这些表达式的结果:
路径表达式 | 结果 |
---|---|
/bookstore/* | 选取 bookstore 元素的所有子元素。 |
//* | 选取文档中的所有元素。 |
//title[@*] | 选取所有带有属性的 title 元素。 |
通过在路径表达式中使用“|”运算符,您可以选取若干个路径。
实例
在下面的表格中,我们列出了一些路径表达式,以及这些表达式的结果:
路径表达式 | 结果 |
---|---|
//book/title | //book/price | 选取 book 元素的所有 title 和 price 元素。 |
//title | //price | 选取文档中的所有 title 和 price 元素。 |
/bookstore/book/title | //price | 选取属于 bookstore 元素的 book 元素的所有 title 元素,以及文档中所有的 price 元素。 |
下面列出了可用在 XPath 表达式中的运算符:( 此表参考来源:http://www.w3school.com.cn/xpath/xpath_operators.asp)
运算符 | 描述 | 实例 | 返回值 |
---|---|---|---|
| | 计算两个节点集 | //book | //cd | 返回所有拥有 book 和 cd 元素的节点集 |
+ | 加法 | 6 + 4 | 10 |
– | 减法 | 6 – 4 | 2 |
* | 乘法 | 6 * 4 | 24 |
div | 除法 | 8 div 4 | 2 |
= | 等于 | price=9.80 | 如果 price 是 9.80,则返回 true。如果 price 是 9.90,则返回 false。 |
!= | 不等于 | price!=9.80 | 如果 price 是 9.90,则返回 true。如果 price 是 9.80,则返回 false。 |
< | 小于 | price<9.80 | 如果 price 是 9.00,则返回 true。如果 price 是 9.90,则返回 false。 |
<= | 小于或等于 | price<=9.80 | 如果 price 是 9.00,则返回 true。如果 price 是 9.90,则返回 false。 |
> | 大于 | price>9.80 | 如果 price 是 9.90,则返回 true。如果 price 是 9.80,则返回 false。 |
>= | 大于或等于 | price>=9.80 | 如果 price 是 9.90,则返回 true。如果 price 是 9.70,则返回 false。 |
or | 或 | price=9.80 or price=9.70 | 如果 price 是 9.80,则返回 true。如果 price 是 9.50,则返回 false。 |
and | 与 | price>9.00 and price<9.90 | 如果 price 是 9.80,则返回 true。如果 price 是 8.50,则返回 false。 |
mod | 计算除法的余数 | 5 mod 2 | 1 |
XPath 函数的高级使用示例:
1.使用 contains() 和 and
//div[starts-with(@id,'res')]//table[1]//tr//td[2]//a//span[contains(.,'_Test') and contains(.,'KPI')]
//div[contains(@id,'in')] ,表示选择id中包含有’in’的div节点
2.text():
由于一个节点的文本值不属于属性,比如“baidu”,
所以,用text()函数来匹配节点://a[text()='baidu']
//span[@id='idHeaderTitleCell' and contains(text(),'QuickStart')]
3.last():
前面已介绍
4. 使用starts-with()
//div[starts-with(@id,'in')] ,表示选择以’in’开头的id属性的div节点
//div[starts-with(@id,'res')]//table//tr//td[2]//table//tr//td//a//span[contains(.,'Developer Tutorial')]
5.not()函数,表示否定。not()函数通常与返回值为true or false的函数组合起来用,
比如contains(),starts-with()等,但有一种特别情况请注意一下:
我们要匹配出input节点含有id属性的,写法为://input[@id],
如果我们要匹配出input节点不含用id属性的,则为://input[not(@id)]
//input[@name=‘identity’ and not(contains(@class,‘a’))] ,表示匹配出name为identity并且class的值中不包含a的input节点。
6.使用descendant
//div[starts-with(@id,'res')]//table[1]//tr//td[2]//a//span[contains(.,'QuickStart')]/../../../descendant::img
7.使用ancestor
//div[starts-with(@id,'res')]//table[1]//tr//td[2]//a//span[contains(.,'QuickStart')]/ancestor::div[starts-with(@id,'res')]//table[2]//descendant::a[2]
scrapy实战2,使用内置的xpath,re 和 css 提取值:https://www.cnblogs.com/regit/p/9629263.html
span 标签 class 属性包含 selectable 字符串://span[contains(@class, 'selectable')]
匹配猫眼 座位数
//div[@class='seats-wrapper']/div/span[contains(@class,'seat') and not(contains(@class,'empty'))]
等价于
//div[@class='seats-wrapper']/div//span[not(contains(//span[contains(@class, 'seat')]/@class, 'empty'))]
./@data-val
//div[contains(@class, "show-list") and @data-index="{0}"]
.//div[@class="show-date"]//span[contains(@class, "date-item")]/text()
.//div[contains(@class, "plist-container")][1]//tbody//tr xpath 中下标是从 1 开始的
substring-before(substring-after(//script[contains(text(), '/apps/feedlist')]/text(), 'html":"'), '"})')
//div[text()="hello"]/p/text()
//a[@class="movie-name"][1]/text()
string(//a[@class="movie-name"][1])
1. 获取父节点属性
首先选中 href 属性为 link4.html的a节点,然后再获取其父节点,然后再获取其class属性
result1 = response.xpath('//a[@href="link4.html"]/../@class')
我们也可以通过parent::来获取父节点
result2 = response.xpath('//a[@href="link4.html"]/parent::*/@class')
注意: //a表示html中的所有a节点,他们的href属性有多个,这里[]的作用是属性匹配,找到a的href属性为link4.html的节点
2. 获取节点内部文本
获取class为item-1的li节点文本,
result3 = response.xpath('//li[@class="item-0"]/a/text()')
返回结果为:['first item', 'fifth item']
3. 属性获取
获取所有li节点下的所有a节点的href属性
result4 = response.xpath('//li/a/@href')
返回结果为:['link1.html', 'link2.html', 'link3.html', 'link4.html', 'link5.html']
4. 按序选择
result = response.xpath('//li[1]/a/text()') #选取第一个li节点
result = response.xpath('//li[last()]/a/text()') #选取最后一个li节点
result = response.xpath('//li[position()<3]/a/text()') #选取位置小于3的li节点,也就是1和2的节点
result = response.xpath('//li[last()-2]/a/text()') #选取倒数第三个节点
5. 节点轴选择
1)返回第一个li节点的所有祖先节点,包括html,body,div和ul
result = response.xpath('//li[1]/ancestor::*')
2)返回第一个li节点的
首先我们利用它来解析 HTML 代码,先来一个小例子来感受一下它的基本用法。
from lxml import etree
text = '''
'''
html = etree.HTML(text)
result = etree.tostring(html)
print(result)
首先我们使用 lxml 的 etree 库,然后利用 etree.HTML 初始化,然后我们将其打印出来。
其中,这里体现了 lxml 的一个非常实用的功能就是自动修正 html 代码,大家应该注意到了,最后一个 li 标签,其实我把尾标签删掉了,是不闭合的。不过,lxml 因为继承了 libxml2 的特性,具有自动修正 HTML 代码的功能。
所以输出结果是这样的
不仅补全了 li 标签,还添加了 body,html 标签。
除了直接读取字符串,还支持从文件读取内容。比如我们新建一个文件叫做 hello.html,内容为
利用 parse 方法来读取文件。
from lxml import etree
html = etree.parse('hello.html')
result = etree.tostring(html, pretty_print=True)
print(result)
同样可以得到相同的结果。
python3解析库 lxml :http://www.cnblogs.com/zhangxinqi/p/9210211.html
依然以上一段程序为例
(1)获取 所有 的
from lxml import etree
html = etree.parse('hello.html')
print type(html)
result = html.xpath('//li')
print result
print len(result)
print type(result)
print type(result[0])
运行结果
[, , , , ]
5
可见,etree.parse 的类型是 ElementTree,
通过调用 xpath 以后,得到了一个列表,包含了 5 个
from lxml import etree
html = etree.parse('hello.html', etree.HTMLParser())
result = html.xpath('//*') # //代表获取子孙节点,*代表获取所有
print(type(html))
print(type(result))
print(result)
# 如要获取li节点,可以使用//后面加上节点名称,然后调用xpath()方法
html.xpath('//li') # 获取所有子孙节点的li节点
(2)获取 子节点
通过 / 或者 // 即可查找元素的 子节点 或者 子孙节点,如果想选择li节点的所有直接a节点,可以这样使用
# 通过追加/a选择所有li节点的所有直接a节点,因为//li用于选中所有li节点,/a用于选中li节点的所有直接子节点a
result=html.xpath('//li/a')
(3)获取 父节点
通过 / 或者 // 可以查找 子节点 或 子孙节点,那么要查找父节点可以使用 .. 来实现也可以使用 parent:: 来获取父节点
from lxml import etree
from lxml.etree import HTMLParser
text='''
'''
html=etree.HTML(text,etree.HTMLParser())
result=html.xpath('//a[@href="link2.html"]/../@class')
result1=html.xpath('//a[@href="link2.html"]/parent::*/@class')
print(result)
print(result1)
'''
['item-1']
['item-1']
'''
(4)属性 匹配
在选取的时候,我们还可以用 @符号 进行属性过滤。比如,这里如果要选取 class 为 link1.html 的 li 节点,可以这样实现:
from lxml import etree
from lxml.etree import HTMLParser
text='''
'''
html=etree.HTML(text, etree.HTMLParser())
result=html.xpath('//li[@class="link1.html"]')
print(result)
# 获取 标签的所有 class
result = html.xpath('//li/@class')
print(result)
(5)文本 获取
我们用XPath中的 text() 方法获取节点中的文本
from lxml import etree
text='''
'''
html=etree.HTML(text,etree.HTMLParser())
result=html.xpath('//li[@class="item-1"]/a/text()') #获取a节点下的内容
result1=html.xpath('//li[@class="item-1"]//text()') #获取li下所有子孙节点的内容
print(result)
print(result1)
(6)属性 获取
使用 @符号即可获取节点的属性,如下:获取所有li节点下所有a节点的href属性
result=html.xpath('//li/a/@href') #获取a的href属性
result=html.xpath('//li//@href') #获取所有li子孙节点的href属性
(7)属性 多值 匹配
如果某个属性的值有多个时,我们可以使用 contains() 函数来获取
from lxml import etree
text1='''
'''
html=etree.HTML(text1,etree.HTMLParser())
result=html.xpath('//li[@class="aaa"]/a/text()')
result1=html.xpath('//li[contains(@class,"aaa")]/a/text()')
print(result)
print(result1)
#通过第一种方法没有取到值,通过contains()就能精确匹配到节点了
[]
['第一个']
(8)多 属性 匹配
另外我们还可能遇到一种情况,那就是根据多个属性确定一个节点,这时就需要同时匹配多个属性,此时可用运用and运算符来连接使用:
from lxml import etree
text1='''
'''
html=etree.HTML(text1,etree.HTMLParser())
result=html.xpath('//li[@class="aaa" and @name="fore"]/a/text()')
result1=html.xpath('//li[contains(@class,"aaa") and @name="fore"]/a/text()')
print(result)
print(result1)
#
['second item']
['second item']
(9)按序 选择
有时候,我们在选择的时候某些属性可能同时匹配多个节点,但我们只想要其中的某个节点,如第二个节点或者最后一个节点,这时可以利用中括号引入索引的方法获取特定次序的节点:
from lxml import etree
text1='''
'''
html=etree.HTML(text1,etree.HTMLParser())
result=html.xpath('//li[contains(@class,"aaa")]/a/text()') #获取所有li节点下a节点的内容
result1=html.xpath('//li[1][contains(@class,"aaa")]/a/text()') #获取第一个
result2=html.xpath('//li[last()][contains(@class,"aaa")]/a/text()') #获取最后一个
result3=html.xpath('//li[position()>2 and position()<4][contains(@class,"aaa")]/a/text()') #获取第一个
result4=html.xpath('//li[last()-2][contains(@class,"aaa")]/a/text()') #获取倒数第三个
print(result)
print(result1)
print(result2)
print(result3)
print(result4)
#
['第一个', '第二个', '第三个', '第四个']
['第一个']
['第四个']
['第三个']
['第二个']
这里使用了last()、position()函数,在XPath中,提供了100多个函数,包括存取、数值、字符串、逻辑、节点、序列等处理功能,它们的具体作用可参考:http://www.w3school.com.cn/xpath/xpath_functions.asp
(10)节点轴 选择
XPath提供了很多节点选择方法,包括获取子元素、兄弟元素、父元素、祖先元素等,示例如下:
from lxml import etree
text1='''
'''
html=etree.HTML(text1,etree.HTMLParser())
result=html.xpath('//li[1]/ancestor::*') #获取所有祖先节点
result1=html.xpath('//li[1]/ancestor::div') #获取div祖先节点
result2=html.xpath('//li[1]/attribute::*') #获取所有属性值
result3=html.xpath('//li[1]/child::*') #获取所有直接子节点
result4=html.xpath('//li[1]/descendant::a') #获取所有子孙节点的a节点
result5=html.xpath('//li[1]/following::*') #获取当前子节之后的所有节点
result6=html.xpath('//li[1]/following-sibling::*') #获取当前节点的所有同级节点
#
[, , , ]
[]
['aaa', 'item']
[]
[]
[, , , , , ]
[, , ]
# 获取
# 获取
# 获取
# 获取最后一个
# 获取倒数第二个元素的内容
result = html.xpath('//li[last()-1]/a')
print result[0].text
# 获取 class 为 bold 的标签名
result = html.xpath('//*[@class="bold"]')
print result[0].tag
以上使用的是XPath轴的用法,更多轴的用法可参考:http://www.w3school.com.cn/xpath/xpath_axes.asp
#!/usr/bin/python3
# -*- coding: utf-8 -*-
# @Author :
# @File : test_1.py
# @Software : PyCharm
# @description : XXX
import requests
from requests.exceptions import RequestException
from lxml import etree
from lxml.etree import ParseError
import json
def one_to_page(html):
headers = {
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 '
'(KHTML, like Gecko) Chrome/67.0.3396.62 Safari/537.36'
}
try:
response = requests.get(html, headers=headers)
body = response.text # 获取网页内容
try:
html = etree.HTML(body, etree.HTMLParser()) # 解析HTML文本内容
result = html.xpath('//table[contains(@class,"table-top20")]/tbody/tr//text()') # 获取列表数据
pos = 0
for i in range(20):
if i == 0:
yield result[i:5]
else:
yield result[pos:pos + 5] # 返回排名生成器数据
pos += 5
except ParseError as e:
print(e.position)
except RequestException as e:
print('request is error!', e)
def write_file(data): # 将数据重新组合成字典写入文件并输出
for i in data:
sul = {
'2018年6月排行': i[0],
'2017年6排行': i[1],
'开发语言': i[2],
'评级': i[3],
'变化率': i[4]
}
with open('test.txt', 'a', encoding='utf-8') as f:
f.write(json.dumps(sul, ensure_ascii=False) + '\n') # 必须格式化数据
f.close()
print(sul)
def main():
url = 'https://www.tiobe.com/tiobe-index/'
data = one_to_page(url)
write_file(data)
if __name__ == '__main__':
main()
'''
{'2018年6月排行': '1', '2017年6排行': '1', '开发语言': 'Java', '评级': '15.932%', '变化率': '+2.66%'}
{'2018年6月排行': '2', '2017年6排行': '2', '开发语言': 'C', '评级': '14.282%', '变化率': '+4.12%'}
{'2018年6月排行': '3', '2017年6排行': '4', '开发语言': 'Python', '评级': '8.376%', '变化率': '+4.60%'}
{'2018年6月排行': '4', '2017年6排行': '3', '开发语言': 'C++', '评级': '7.562%', '变化率': '+2.84%'}
{'2018年6月排行': '5', '2017年6排行': '7', '开发语言': 'Visual Basic .NET', '评级': '7.127%', '变化率': '+4.66%'}
{'2018年6月排行': '6', '2017年6排行': '5', '开发语言': 'C#', '评级': '3.455%', '变化率': '+0.63%'}
{'2018年6月排行': '7', '2017年6排行': '6', '开发语言': 'JavaScript', '评级': '3.063%', '变化率': '+0.59%'}
{'2018年6月排行': '8', '2017年6排行': '9', '开发语言': 'PHP', '评级': '2.442%', '变化率': '+0.85%'}
{'2018年6月排行': '9', '2017年6排行': '-', '开发语言': 'SQL', '评级': '2.184%', '变化率': '+2.18%'}
{'2018年6月排行': '10', '2017年6排行': '12', '开发语言': 'Objective-C', '评级': '1.477%', '变化率': '-0.02%'}
{'2018年6月排行': '11', '2017年6排行': '16', '开发语言': 'Delphi/Object Pascal', '评级': '1.396%', '变化率': '+0.00%'}
{'2018年6月排行': '12', '2017年6排行': '13', '开发语言': 'Assembly language', '评级': '1.371%', '变化率': '-0.10%'}
{'2018年6月排行': '13', '2017年6排行': '10', '开发语言': 'MATLAB', '评级': '1.283%', '变化率': '-0.29%'}
{'2018年6月排行': '14', '2017年6排行': '11', '开发语言': 'Swift', '评级': '1.220%', '变化率': '-0.35%'}
{'2018年6月排行': '15', '2017年6排行': '17', '开发语言': 'Go', '评级': '1.189%', '变化率': '-0.20%'}
{'2018年6月排行': '16', '2017年6排行': '8', '开发语言': 'R', '评级': '1.111%', '变化率': '-0.80%'}
{'2018年6月排行': '17', '2017年6排行': '15', '开发语言': 'Ruby', '评级': '1.109%', '变化率': '-0.32%'}
{'2018年6月排行': '18', '2017年6排行': '14', '开发语言': 'Perl', '评级': '1.013%', '变化率': '-0.42%'}
{'2018年6月排行': '19', '2017年6排行': '20', '开发语言': 'Visual Basic', '评级': '0.979%', '变化率': '-0.37%'}
{'2018年6月排行': '20', '2017年6排行': '19', '开发语言': 'PL/SQL', '评级': '0.844%', '变化率': '-0.52%'}
'''
#!/usr/bin/python3
# -*- coding: utf-8 -*-
# @Author :
# @File : shijing.py
# @Software : PyCharm
# @description : XXX
import json
import traceback
import requests
from lxml import etree
"""
step1: 安装 lxml 库。
step2: from lxml import etree
step3: selector = etree.HTML(网页源代码)
step4: selector.xpath(一段神奇的符号)
"""
def parse():
url = 'https://www.gushiwen.org/guwen/shijing.aspx'
r = requests.get(url)
if r.status_code == 200:
selector = etree.HTML(r.text)
s_all_type_content = selector.xpath('//div[@class="sons"]/div[@class="typecont"]')
print(len(s_all_type_content))
article_list = list()
for s_type_content in s_all_type_content:
book_m1 = s_type_content.xpath('.//strong/text()')[0].encode('utf-8').decode('utf-8')
s_all_links = s_type_content.xpath('.//span/a')
article_dict = dict()
for s_link in s_all_links:
link_name = s_link.xpath('./text()')[0].encode('utf-8').decode('utf-8')
try:
link_href = s_link.xpath('./@href')[0].encode('utf-8').decode('utf-8')
except BaseException as e:
link_href = None
article_dict[link_name] = link_href
temp = dict()
temp[book_m1] = article_dict
article_list.append(temp)
print(json.dumps(article_list, ensure_ascii=False, indent=4))
else:
print(r.status_code)
if __name__ == '__main__':
parse()
pass
CSS 选择器 参考手册:http://www.w3school.com.cn/cssref/css_selectors.asp
CSS 选择器 :http://www.runoob.com/cssref/css-selectors.html
Selenium之CSS Selector定位详解:https://www.bbsmax.com/A/MyJxLGE1Jn/
css selector
CSS选择器用于选择你想要的元素的样式的模式。
"CSS"列表示在CSS版本的属性定义(CSS1,CSS2,或对CSS3)。
选择器 | 示例 | 示例说明 | CSS |
---|---|---|---|
.class | .intro | 选择所有class="intro"的元素 | 1 |
#id | #firstname | 选择所有id="firstname"的元素 | 1 |
* | * | 选择所有元素 | 2 |
element | p | 选择所有 元素 |
1 |
element,element | div,p | 选择所有 元素和 元素 |
1 |
element element | div p | 选择 元素内的所有 元素 |
1 |
element>element | div>p | 选择所有父级是 元素的 元素 |
2 |
element+element | div+p | 选择所有紧接着 元素之后的 元素 |
2 |
[attribute] | [target] | 选择所有带有target属性元素 | 2 |
[attribute=value] | [target=-blank] | 选择所有使用target="-blank"的元素 | 2 |
[attribute~=value] | [title~=flower] | 选择标题属性包含单词"flower"的所有元素 | 2 |
[attribute|=language] | [lang|=en] | 选择 lang 属性以 en 为开头的所有元素 | 2 |
:link | a:link | 选择所有未访问链接 | 1 |
:visited | a:visited | 选择所有访问过的链接 | 1 |
:active | a:active | 选择活动链接 | 1 |
:hover | a:hover | 选择鼠标在链接上面时 | 1 |
:focus | input:focus | 选择具有焦点的输入元素 | 2 |
:first-letter | p:first-letter | 选择每一个 元素的第一个字母 |
1 |
:first-line | p:first-line | 选择每一个 元素的第一行 |
1 |
:first-child | p:first-child | 指定只有当 元素是其父级的第一个子级的样式。 |
2 |
:before | p:before | 在每个 元素之前插入内容 |
2 |
:after | p:after | 在每个 元素之后插入内容 |
2 |
:lang(language) | p:lang(it) | 选择一个lang属性的起始值="it"的所有 元素 |
2 |
element1~element2 | p~ul | 选择p元素之后的每一个ul元素 | 3 |
[attribute^=value] | a[src^="https"] | 选择每一个src属性的值以"https"开头的元素 | 3 |
[attribute$=value] | a[src$=".pdf"] | 选择每一个src属性的值以".pdf"结尾的元素 | 3 |
[attribute*=value] | a[src*="runoob"] | 选择每一个src属性的值包含子字符串"runoob"的元素 | 3 |
:first-of-type | p:first-of-type | 选择每个p元素是其父级的第一个p元素 | 3 |
:last-of-type | p:last-of-type | 选择每个p元素是其父级的最后一个p元素 | 3 |
:only-of-type | p:only-of-type | 选择每个p元素是其父级的唯一p元素 | 3 |
:only-child | p:only-child | 选择每个p元素是其父级的唯一子元素 | 3 |
:nth-child(n) | p:nth-child(2) | 选择每个p元素是其父级的第二个子元素 | 3 |
:nth-last-child(n) | p:nth-last-child(2) | 选择每个p元素的是其父级的第二个子元素,从最后一个子项计数 | 3 |
:nth-of-type(n) | p:nth-of-type(2) | 选择每个p元素是其父级的第二个p元素 | 3 |
:nth-last-of-type(n) | p:nth-last-of-type(2) | 选择每个p元素的是其父级的第二个p元素,从最后一个子项计数 | 3 |
:last-child | p:last-child | 选择每个p元素是其父级的最后一个子级。 | 3 |
:root | :root | 选择文档的根元素 | 3 |
:empty | p:empty | 选择每个没有任何子级的p元素(包括文本节点) | 3 |
:target | #news:target | 选择当前活动的#news元素(包含该锚名称的点击的URL) | 3 |
:enabled | input:enabled | 选择每一个已启用的输入元素 | 3 |
:disabled | input:disabled | 选择每一个禁用的输入元素 | 3 |
:checked | input:checked | 选择每个选中的输入元素 | 3 |
:not(selector) | :not(p) | 选择每个并非p元素的元素 | 3 |
::selection | ::selection | 匹配元素中被用户选中或处于高亮状态的部分 | 3 |
:out-of-range | :out-of-range | 匹配值在指定区间之外的input元素 | 3 |
:in-range | :in-range | 匹配值在指定区间之内的input元素 | 3 |
:read-write | :read-write | 用于匹配可读及可写的元素 | 3 |
:read-only | :read-only | 用于匹配设置 "readonly"(只读) 属性的元素 | 3 |
:optional | :optional | 用于匹配可选的输入元素 | 3 |
:required | :required | 用于匹配设置了 "required" 属性的元素 | 3 |
:valid | :valid | 用于匹配输入值为合法的元素 | 3 |
:invalid | :invalid | 用于匹配输入值为非法的元素 | 3 |
CSS选择器的常见语法:
1. 根据 标签定位 tagName (定位的是一组,多个元素)
find_element_by_css_selector("div")
2. 根据 id属性 定位 (注意 id 使用 # 表示)
find_element_by_css_selector("#eleid")
find_element_by_css_selector("div#eleid")
3. 根据 className 属性 定位(注意 class 属性 使用.)
两种方式:前面加上 tag 名称。也可以不加。如果不加 tag 名称时,点不能省略。
find_element_by_css_selector('.class_value')
find_element_by_css_selector("div.eleclass")
find_element_by_css_selector('tag_name.class_value')
有的 class_value 比较长,而且中间有空格时,不能把空格原样写进去,那样不能识别。
这时,空格用点代替,前面要加上 tag_name。
driver.find_element_by_css_selector('div.panel.panel-email').click()
#
This paragraph is a very important warning.
注意:如果 class属性值 里带空格,用.来代替空格
4.2 模糊匹配
find_element_by_css_selector("div[name^=elename]") #从起始位置开始匹配
find_element_by_css_selector("div[name$=name2]") #从结尾匹配
find_element_by_css_selector("div[name*=name1]") #从中间匹配,包含
4.3 多属性匹配
find_element_by_css_selector("div[type='eletype][value='elevalue']") #同时有多属性
find_element_by_css_selector("div.eleclsss[name='namevalue'] #选择class属性为eleclass并且name为namevalue的div节点
find_element_by_css_selector("div[name='elename'][type='eletype']:nth-of-type(1) #选择name为elename并且type为eletype的第1个div节点
5. 定位子元素 (A>B)
find_element_by_css_selector("div#eleid>input") #选择id为eleid的div下的所有input节点
find_element_by_css_selector("div#eleid>input:nth-of-type(4) #选择id为eleid的div下的第4个input节点
find_element_by_css_selector("div#eleid>nth-child(1)") #选择id为eleid的div下的第一个子节点
6. 定位后代元素 (A空格B)
find_element_by_css_selector("div#eleid input") #选择id为eleid的div下的所有的子孙后代的 input 节点
find_element_by_css_selector("div#eleid>input:nth-of-type(4)+label #选择id为eleid的div下的第4个input节点的相邻的label节点
find_element_by_css_selector("div#eleid>input:nth-of-type(4)~label #选择id为eleid的div下的第4个input节点之后中的所有label节点
7. 不是 ( 否 )
find_element_by_css_selector("div#eleid>*.not(input)") #选择id为eleid的div下的子节点中不为input 的所有子节点
find_element_by_css_selector("div:not([type='eletype'])") #选择div节点中type不为eletype的所有节点
8. 包含Bycontent
find_element_by_css_selector("li:contains('Goa')") #
9. by index
find_element_by_css_selector("li:nth(5)")
10. 路径法
两种方式,可以在前面加上 tag 名称,也可以不加。注意它的层级关系使用大于号">"。
find_element_by_css_selector("form#loginForm>ul>input[type='password']").send_keys('密码')
高阶:
CSS 选择器中,最常用的选择器 如下:
选择器 | 描述 | 举例 |
* | 通配选择器,选择所有的元素 | * |
选择特定类型的元素,支持基本HTML标签 | h1 | |
. |
选择具有特定class的元素。 | .class1 |
特定类型和特定class的交集。(直接将多个选择器连着一起表示交集) | h1.class1 | |
# |
选择具有特定id属性值的元素 | #id1 |
除了最基本的核心选择器外,还有可以 基于属性 的 属性选择器:
选择器 | 描述 | 举例 |
[attr] | 选取定义attr属性的元素,即使该属性没有值 | [placeholder] |
[attr="val"] | 选取attr属性等于val的元素 | [placeholder="请输入关键词"] |
[attr^="val"] | 选取attr属性开头为val的元素 | [placeholder^="请输入"] |
[attr$="val"] | 选取attr属性结尾为val的元素 | [placeholder$="关键词"] |
[attr*="val"] | 选取attr属性包含val的元素 | [placeholder*="入关"] |
[attr~="val"] | 选取attr属性包含多个空格分隔的属性,其中一个等于val的元素 | [placeholder~="关键词"] |
[attr|="val"] | 选取attr属性等于val的元素或第一个属性值等于val的元素 | [placeholder|="关键词"] |
This paragraph is a very important warning.
有一些选择器是基于层级之间的关系,这类选择器称之为关系选择器。
选择器 | 描述 | 举例 |
第二个选择器为第一个选择器的后代元素,选取第二个选择器匹配结果 | .class1 h1 | |
第二个选择器为第一个选择器的直接子元素,选取第二个选择器匹配结果 | .class1 > * | |
第二个选择器为第一个选择器的兄弟元素,选取第二个选择器的下一兄弟元素 | .class1 + [lang] | |
第二个选择器为第一个选择器的兄弟元素,选取第二个选择器的全部兄弟元素 | .class1 ~ [lang] |
选择 某个元素 的 后代的元素:
selenium举例:(By.CSS_SELECTOR,‘div button’)
div元素的所有的后代元素中标签为button元素,不管嵌套有多深
选择 某个元素 的 子代元素:
selenium举例:(By.CSS_SELECTOR,‘div > button’)
div元素的所有的子代元素中标签为button元素(>符号前后的空格可有可无)
一个元素不好定位时,它的兄长元素很起眼,可以借助兄长来扬名,因此不妨称之为 "弟弟选择器".
即选择某个元素的弟弟元素(先为兄,后为弟):
selenium举例: (By.CSS_SELECTOR,'button + li')
button与li属于同一父元素,且button与li相邻,选择button下标签为li的元素
利用 联合选择器与反选择器,可以实现 与和或 的关系。
选择器 | 描述 | 举例 |
属于第一个选择器的元素或者是属于第二个选择器的元素 | h1, h2 | |
:not( |
不属于选择器选中的元素 | :not(html) |
CSS选择器支持了 伪元素和伪类选择器。
:active | 鼠标点击的元素 |
:checked | 处于选中状态的元素 |
:default | 选取默认值的元素 |
:disabled | 选取处于禁用状态的元素 |
:empty | 选取没有任何内容的元素 |
:enabled | 选取处于可用状态的元素 |
:first-child | 选取元素的第一个子元素 |
:first-letter | 选取文本的第一个字母 |
:first-line | 选取文本的第一行 |
:focus | 选取得到焦点的元素 |
:hover | 选取鼠标悬停的元素 |
:in-range | 选取范围之内的元素 |
:out-of-range | 选取范围之外的元素 |
:lang( |
选取lang属性为language的元素 |
:last-child | 选取元素的最后一个子元素 |