1. Xpath
1 )什么是XPath?
xpath(XML Path Language)是一门在XML和HTML文档中查找信息的语言,可用来在XML和HTML文档中对元素和属性进行遍历。
2) XPath开发工具
- Chrome插件XPath Helper。
- Firefox插件Try XPath。
1.1Xpath语法
xml version="1.0" encoding="ISO-8859-1"?> <bookstore> <book> <title lang="eng">Harry Pottertitle> <price>29.99price> book> <book> <title lang="eng">Learning XMLtitle> <price>39.95price> book> bookstore>
1.1.1 选取节点
XPath 使用路径表达式在 XML 文档中选取节点。节点是通过沿着路径或者 step 来选取的。
下面列出了最有用的路径表达式:
表达式 | 描述 |
---|---|
nodename | 选取此节点的所有子节点。 |
/ | 如果是在最前面,代表从根节点选取。否则选择某节点下的直接子节点 |
// | 从匹配选择的当前节点选择文档中的节点,而不考虑它们的位置。 |
. | 选取当前节点。 |
.. | 选取当前节点的父节点。 |
@ | 选取属性。 |
实例:在下面的表格中,列出了一些路径表达式以及表达式的结果:
路径表达式 | 结果 |
---|---|
bookstore | 选取 bookstore 元素的所有子节点。 |
/bookstore | 选取根元素 bookstore。 注释:假如路径起始于正斜杠( / ),则此路径始终代表到某元素的绝对路径! |
bookstore/book | 选取属于 bookstore 的子元素的所有 book 元素。 |
//book | 选取所有 book 子元素,而不管它们在文档中的位置。 |
bookstore//book | 选择属于 bookstore 元素的后代的所有 book 元素,而不管它们位于 bookstore 之下的什么位置。 |
//book[@lang] | 选取所有拥有lang属性的book节点。 |
1.1.2 谓语(Predicates)
谓语用来查找某个特定的节点或者包含某个指定的值的节点。
谓语被嵌在方括号中。
实例:在下面的表格中,列出了带有谓语的一些路径表达式,以及表达式的结果:
路径表达式 | 结果 |
---|---|
/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。 |
1. 1.3 通配符
XPath 通配符可用来选取未知的 XML 元素。
通配符 | 描述 |
---|---|
* | 匹配任何元素节点。 |
@* | 匹配任何属性节点。 |
node() | 匹配任何类型的节点。 |
实例:在下面的表格中,我们列出了一些路径表达式,以及这些表达式的结果:
路径表达式 | 结果 |
---|---|
/bookstore/* | 选取 bookstore 元素的所有子元素。 |
//* | 选取文档中的所有元素。 |
//title[@*] | 选取所有带有属性的 title 元素。 |
1.1.4 选取若干路径
通过在路径表达式中使用“|”运算符,您可以选取若干个路径。
实例:在下面的表格中,我们列出了一些路径表达式,以及这些表达式的结果:
路径表达式 | 结果 |
---|---|
//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/index.asp
2. lxml库
lxml是python的一个解析库,支持HTML和XML的解析,支持XPath解析方式,而且解析效率非常高
2.1 lxml库常用类的属性和方法
object ---+ | _Element # ===================================== # Properties(属性) # ===================================== attrib # 元素属性字典 base # 原始文档的url或None sourceline # 原始行数或None tag # tag名 tail # 尾巴文本(存在于兄弟节点间,属于父节点的文本内容) text # 位于第一个子标签之前的子文本 prefix # 命名空间前缀(XML)(详解见底部附录) nsmap # 命名空间与URL映射关系(XML)(详解见底部附录) # ===================================== # Instance Methods(实例方法)(常用) # ===================================== xpath(self, _path, namespaces=None, extensions=None, smart_strings=True, **_variables) # 通过xpath表达式查找指定节点元素,返回指定节点元素列表或None getparent(self) # 查找父节点,返回找到的父节点对象或None getprevious(self) # 查找前一个相邻的兄弟节点元素,返回找到的节点对象或None getnext(self) # 查找后一个相邻的兄弟节点对象,返回找到的节点对象或None getchildren(self) # 返回所有直属的子节点对象 getroottree(self) # 返回所在文档的根节点树 find(self, path, namespaces=None) # 根据标签名或路径,返回第一个匹配到的子节点对象 findall(self, path, namespaces=None) # 根据标签名或路径,返回全部符合要求的子节点对象 findtext(self, path, default=None, namespaces=None) # 根据标签名或路径,返回第一个匹配到的子节点对象的text文本 clear(self) # 重置节点对象,清除所有子节点对象,以及所有的text、tail对象 get(self, key, default=None) # 返回节点属性key对应的值 items(self) # 以任意顺序返回节点属性键和值 keys(self) # 以任意顺序返回包含节点全部属性名的列表 values(self) # 以任意顺序返回包含节点全部属性值的列表 set(self, key, value) # 设置节点属性
object ---+ | _Element ---+ | ElementBase # ===================================== Functions(函数)(常用) # ===================================== HTML(text, parser=None, base_url=None) # 将字符型HTML文档内容转换为节点树对象 fromstring(text, parser=None, base_url=None) # 将字符型XML文档或文档片段转换问节点树对象 tostring(element_or_tree, encoding=None, method="xml", xml_declaration=None, pretty_print=False, with_tail=True, standalone=None, doctype=None, exclusive=False, with_commments=True, inclusive_ns_prefixes=None) # 将节点树对象序列化为编码的字符型 tounicode(element_or_tree, method="xml", pretty_print=False, with_tail=True, doctype=None) # 将节点树对象序列化为Unicode型
object ---+ | etree._Element ---+ | etree.ElementBase---+ | object ---+ | | | HtmlMixin ---+ | HtmlElement # ===================================== Functions(函数)(常用) # ===================================== fromstring(html, base_url=None, parser=None, **kwargs) # 将字符型html文档转换为节点树或文档树 tostring(doc, pretty_print=False, include_meta_content_type=False, encoding=None, method="html", with_tail=True, doctype=None) # 将节点树或文档树序列化为字符型 ###################################### **Class HtmlMixin** object ---+ | HtmlMixin # ===================================== Properties(属性) # ===================================== base_url # 文档url head # 标签部分 body # 标签部分 forms # 返回全部form列表 label # 元素的label标签 classes # class属性值的集合 # ===================================== Instance Methods(实例方法)(常用) # ===================================== drop_tag(self) # 移除标签,但不移除其子标签和text文本,将其合并到父节点 drop_tree(self) # 移除节点树(包含子节点和text),但不移除它的tail文本,将其合并到父节点或前一个兄弟节点 find_class(self, class_name) # 根据class属性值查找节点元素 get_element_by_id(self, rel) # 根据id属性值查找节点元素 set(self, key, value=None) # 设置节点元素的属性 text_content(self) # 返回其后代节点与其自身的全部text内容
2.2 从字符串中解析HTML代码
解析html字符串,使用'lxml.etree.HTML'进行解析。
# 使用 lxml 的 etree 库 from lxml import etree text = '''''' #利用etree.HTML,将字符串解析为HTML文档 htmlElementTree = etree.HTML(text) # 按字符串序列化HTML文档 result = etree.tostring(htmlElementTree,encoding='utf-8') .decode('utf-8')) print(result)
- first item
- second item
- third item
- fourth item
- fifth item # 注意,此处缺少一个
闭合标签
输出结果如下:
<html><body> <div> <ul> <li class="item-0"><a href="link1.html">first itema>li> <li class="item-1"><a href="link2.html">second itema>li> <li class="item-inactive"><a href="link3.html">third itema>li> <li class="item-1"><a href="link4.html">fourth itema>li> <li class="item-0"><a href="link5.html">fifth itema>li> ul> div> body>html>
可以看到。lxml会自动修改HTML代码。例子中不仅补全了li标签,还添加了body,html标签。
2.3 从文件中解析html代码
除了直接使用字符串进行解析,lxml还支持从文件中读取内容。我们新建一个hello.html文件:
<div> <ul> <li class="item-0"><a href="link1.html">first itema>li> <li class="item-1"><a href="link2.html">second itema>li> <li class="item-inactive"><a href="link3.html"><span class="bold">third itemspan>a>li> <li class="item-1"><a href="link4.html">fourth itema>li> <li class="item-0"><a href="link5.html">fifth itema>li> ul> div>
解析html文件,使用lxml.etree.parse()进行解析,这个函数默认使用XMLparser解析器,所以如果遇到一些不规范的HTML代码就会解析错误,此时需要自己创建HTMLparser解析器。示例代码如下:
from lxml import etree # 读取外部文件 hello.html parser = etree.HTMLParser()#指定解析器HTMLParser,解析时会根据文件修复HTML文件中缺失的信息 htmlElementTree = etree.parse('hello.html',parser = parser) result = etree.tostring(htmlElementTree,encoding = 'utf-8',pretty_print=True).decode('utf-8') print(result)
输出结果和之前是相同的。
2.4 Xpath与lxml结合
#-*-coding = utf-8 -*- from lxml import etree import requests #爬取豆瓣电影热映电影信息 headers = { "User-Agent":'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36' } response = requests.request(method='get',url='https://movie.douban.com',headers=headers) text = response.text parser = etree.HTMLParser() html = etree.fromstring(text,parser=parser) ul = html.xpath('//ul[@class="ui-slide-content"]')[0] li_list = ul.xpath('./li') move_list = [] for li in li_list: if li.xpath('./@data-title')!= []: data_title = li.xpath('./@data-title') data_release = li.xpath('./@date-release') data_rate = li.xpath('./@data-rate') data_duration = li.xpath('./@data-duration') data_director = li.xpath('./@data-director') data_actors = li.xpath('./@data-actors') data_postor = li.xpath('.//img/@src') data = { 'data_title':data_title, 'data_release':data_release, 'data_rate':data_rate, 'data_duration':data_duration, 'data_director':data_director, 'data_actors':data_actors, 'data_postor':data_postor } move_list.append(data) print(move_list)
以下面的xml练习lxml结合Xpath语法查找感兴趣的元素
xml version="1.0" encoding="utf8"?> <bookstore> <book> <title lang="eng">Harry Pottertitle> <price>29.99price> book> <book> <title lang="eng">Learning XMLtitle> <price>39.95price> book> bookstore>
xml="""""" #1)得到根节点 root = etree.fromstring(xml.encode('utf-8'))# Harry Potter 29.99 Learning XML 39.95 #2)选取所有book子元素,注意xpath()方法返回的是列表 booklist=root.xpath('book')#[ , #3)选取根节点bookstore bookstore = root.xpath('/bookstore')#[] ] #4)选取所有book子元素的title子元素 titlelist1 = root.xpath('/bookstore/book/title')#[, titlelist2 = root.xpath('book/title')#[] , #5)以根节点为始祖,选取其后代的title元素 titlelist = root.xpath('//title')#[] , #6)以book子元素为始祖,选取后代中的price元素 pricelist = root.xpath('book//price')#[] , #7)以根节点为始祖,选取其后代的lang属性值 langValue = root.xpath('//@lang')#['eng', 'eng'] #8)获取bookstore的第一个book子元素 book = root.xpath('/bookstore/book[1]')#[] ] #9)获取bookstore的最后一个book子元素 book_last = root.xpath('/bookstore/book[last()]')#[] #10)选取bookstore的倒数第二个book子元素 print(root.xpath('/bookstore/book[last()-1]'))#[] #11)选取bookstore的前两个book子元素 print(root.xpath('/bookstore/book[position()<3]'))#[, #12)以根节点为始祖,选取其后代中含有lang属性的title元素 print(root.xpath('//title[@lang]'))#[] , #13)以根节点为始祖,选取其后代中含有lang属性并且其值为eng的title元素 print(root.xpath("//title[@lang='eng']"))#[] , #14)选取bookstore子元素book,条件是book的price子元素要大于35 print(root.xpath('/bookstore/book[price>35.00]'))#[] ] #15)选取bookstore子元素book的子元素title,条件是book的price子元素要大于35 print(root.xpath('/bookstore/book[price>35.00]/title'))#[] #16)选取bookstore的所有子元素 print(root.xpath('/bookstore/*'))#[, #17)选取根节点的所有后代元素 print(root.xpath('//*'))#[] , #18)选取根节点的所有具有属性的title元素 print(root.xpath('//title[@*]'))#[, , , , , ] , #19)选取当前节点下的所有节点。'\n'是文本节点 print(root.xpath('node()'))#['\n ',] , '\n ', #20)选取根节点所有后代节点,包括元素、属性、文本 print(root.xpath('//node()'))#[, '\n'] , '\n ', #21)选取所有book的title元素或者price元素 print(root.xpath('//book/title|//book/price'))#[, '\n ', , 'Harry Potter', '\n ', , '29.99', '\n ', '\n ', , '\n ', , 'Learning XML', '\n ', , '39.95', '\n ', '\n'] , #22)选取所有的title或者price元素 print(root.xpath('//title|//price'))#[, , ] , xml_1=""", , ] """ #23)获取所有price的文本内容 root = etree.fromstring(xml_1.encode('utf-8'),parser=etree.HTMLParser()) #way1 print(root.xpath('//price/text()'))#['29.99', '39.95'], print(type(root.xpath('//price/text()')[0]))#返回的是一个 Harry Potter 29.99 分部内容 HarryPotter and the Philosopher's Stone
1.大难不死的男孩
2.悄悄消失的玻璃
3.猫头鹰传书
4.钥匙保管员HarryPotter and the Chamber of Secrets HarryPotter and the Prisoner of Azkaban HarryPotter and the Prisoner of Azkaban Learning XML 39.95 #way2 price_list = root.xpath('//price') for price in price_list: print(price.xpath("string(.)"))#如果匹配的标签是多个,直接用xpath的string(.)方法会报错,如:root.xpath('//price/string(.)') #29.99 #39.95 print(root.xpath('//content/part1/text()'))#["\n HarryPotter and the Philosopher's Stone\n ", '\n 1.大难不死的男孩\n ', '\n 2.悄悄消失的玻璃\n ', '\n 3.猫头鹰传书\n ', '\n 4.钥匙保管员\n '] #24)注意 #1.使用'xpath'语法,应该使用'Element.xpath'方法来选择感兴趣的元素.’xpath函数返回来的永远是一个列表。 #2.获取某个标签的属性:href = html.xpath('//a/@href') #3.获取某个标签的文本,通过xpath中的'text()'函数,root.xpath('//price/text()')
#-*-coding = utf-8 -*- from lxml import etree import requests BASE_DOMAIN = 'https://www.dytt8.net' headers = { 'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36' } def get_detail_urls(url): response = requests.request(method='get',url=url,headers=headers) html=response.text parser = etree.HTMLParser() root = etree.fromstring(html,parser=parser) movies_url_list = root.xpath('//table[@class="tbspan"]//a/@href') #movies_urls = list(map(lambda url:BASE_DOMAIN + url,movies_url_list)) movies_urls = list(map(lambda url:''.join((BASE_DOMAIN,url)),movies_url_list)) return movies_urls def parse_detail_page(url): movie = {} response = requests.request(method='get',url=url,headers=headers) html = response.content.decode('gbk') parser = etree.HTMLParser() root = etree.fromstring(html, parser=parser) title = root.xpath('//h1/font[@color="#07519a"]/text()')[0] movie['title'] = title zoom = root.xpath('//div[@id="Zoom"]')[0] infors = zoom.xpath('.//p/text()') for index,infor in enumerate(infors): if infor.startswith('◎年 代'): movie['年代'] = infor.replace('◎年 代','').strip() elif infor.startswith('◎产 地'): movie['产地'] = infor.replace('◎产 地','').strip() elif infor.startswith('◎类 别'): movie['类别'] = infor.replace('◎类 别', '').strip() elif infor.startswith('◎语 言'): movie['语言'] = infor.replace('◎语 言', '').strip() elif infor.startswith('◎字 幕'): movie['字幕'] = infor.replace('◎字 幕', '').strip() elif infor.startswith('◎豆瓣评分'): movie['豆瓣评分'] = infor.replace('◎豆瓣评分', '').strip() elif infor.startswith('◎片 长'): movie['片长'] = infor.replace('◎片 长', '').strip() elif infor.startswith('◎导 演'): movie['导演'] = infor.replace('◎导 演', '').strip() elif infor.startswith('◎主 演'): movie['主演'] = [] movie['主演'].append(infor.replace('◎主 演', '').strip()) for infor in infors[index+1:len(infors)]: if infor.startswith('◎'): break movie['主演'].append(infor.strip()) elif infor.startswith('◎简 介'): profile = infor.replace('◎简 介', '').strip() for infor in infors[index+1:len(infors)]: profile = profile + infor.strip() movie['简介'] = profile movie['下载地址'] = root.xpath('//td[@bgcolor = "#fdfddf"]/a/@href')[0] return movie def spider(): #url = 'https://www.dytt8.net/html/gndy/dyzz/list_23_1.html' base_url = 'https://www.dytt8.net/html/gndy/dyzz/list_23_{}.html' movies = [] for i in range(1,2): url = base_url.format(i) movies_urls = get_detail_urls(url) for detail_url in movies_urls: movie = parse_detail_page(detail_url) movies.append(movie) return movies if __name__ == '__main__': movies = spider() print(movies)
>>>>>待续