Xpath
Xpath 即为 xml 路径语言(XML Path Language),它是一种用来确定 XML 文档中某部分位置的语言,能够对 XML/HTML 文档中的元素进行遍历和查找。
示例 HTML 片段
<?xml version="1.0" encoding="UTF-8"?>
<bookstore>
<book>
<title lang="eng">Harry Potter</title>
<price>29.99</price>
</book>
<book>
<title lang="eng">Learning XML</title>
<price>39.95</price>
</book>
</bookstore>
节点
在 XPath 中,有七种类型的节点:元素、属性、文本、命名空间、处理指令、注释以及文档(根)节点。XML 文档是被作为节点树来对待的。树的根被称为文档节点或者根节点。
上面的 XML 文档中:
<bookstore> (文档节点)
<title lang="eng">Harry Potter</title> (元素节点)
lang="eng" (属性节点)
Harry Potter (文本节点)
选取节点
表达式 | 描述 | 路径表达式 | 结果 |
nodename | 选择此节点的所有子节点 | bookstore | 选取 bookstore 元素的所有子节点 |
/ | / 在最前面表示从根节点开始选取 否则表示从某节点下开始选取 | /bookstore bookstore/book | 选取根元素 bookstore 选取属于 bookstore 的子元素的所有 book 元素 |
// | 从匹配选择的当前节点选择文档中的节点,而不考虑它们的位置 | //book bookstore//book | 选取所有 book 子元素,而不管它们在文档中的位置 选择属于 bookstore 元素的后代的所有 book 元素,而不管它们位于 bookstore 之下的什么位置 |
@ | 选取属性 | //@lang | 选取名为 lang 的所有属性 |
. | 选取当前节点 | ./a | 选择当前节点下的 a 标签 |
谓语
谓语用来查找某个特定的节点或者包含某个指定的值的节点。谓语被嵌在方括号中。
表达式 | 结果 |
/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 |
通配符
通配符 | 描述 | 路径表达式 | 结果 |
* | 匹配任何元素节点 | /bookstore/* | 选取 bookstore 元素的所有子元素 |
@* | 匹配任何属性节点 | //* | 选取文档中的所有元素 |
node() | 匹配任何类型的节点 | //title[@*] | 选取所有带有属性的 title 元素 |
选取若干路径
使用 | 可以选取多个路径。
路径表达式 | 结果 |
//book/title | //book/price | 选取 book 元素的所有 title 和 price 元素 |
//title | //price | 选取文档中的所有 title 和 price 元素 |
/bookstore/book/title | //price | 选取属于 bookstore 元素的 book 元素的所有 title 元素,以及文档中所有的 price 元素 |
运算符
能够用于 XPath 中的运算符都有:
运算符 | 描述 | 实例 | 返回值 |
---|---|---|---|
| | 计算两个节点集 | //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 |
LXML
使用 lxml 库能够解析和提取 XML/HTML 数据。并且当 HTML 代码不规范的话,lxml 能够进行自动补全。
从字符串读取 XML 数据
from lxml import etree
text = '''
<div>
<ul>
<li class="a"><a href="aaa.html">aaa</a></li>
<li class="b"><a href="bbb.html">bbb</a></li>
<li class="c"><a href="ccc.html">ccc</a></li>
<li class="b"><a href="ddd.html">ddd</a></li>
<li class="a"><a href="eee.html">eee</a>
</ul>
</div>
'''
html = etree.HTML(text)
result = etree.tostring(html)
print(result)
结果为:
<html><body><div>
<ul>
<li class="a"><a href="aaa.html">aaa</a></li>
<li class="b"><a href="bbb.html">bbb</a></li>
<li class="c"><a href="ccc.html">ccc</a></li>
<li class="b"><a href="ddd.html">ddd</a></li>
<li class="a"><a href="eee.html">eee</a>
</li></ul>
</div>
</body></html>
可以看到使用上边的代码能够正确的输出原始的 XML 文本,并将所缺失的部分进行了补全,添加了 body 和 html 标签。
从文件中读取 XML 数据
将之前的 text 文本存储到文件 text.html 中,然后利用 lxml 读取该文件中的数据。
from lxml import etree
html = etree.parse('text.html')
result = etree.tostring(html)
print(result.decode('utf-8'))
结果为:
<div>
<ul>
<li class="a"><a href="aaa.html">aaa</a></li>
<li class="b"><a href="bbb.html">bbb</a></li>
<li class="c"><a href="ccc.html">ccc</a></li>
<li class="b"><a href="ddd.html">ddd</a></li>
<li class="a"><a href="eee.html">eee</a></li>
</ul>
</div>
使用这种方式能够从文件中读取 XML 数据,但是此时需要将缺失的 XML 标签补齐,保证 XML 语法正确,不然会报 XMLSyntaxError 错误。
使用 XPath 语法
from lxml import etree
html = etree.parse('text.html')
# 获取所有的 li 标签
result = html.xpath('//li')
for lable in result:
print(etree.tostring(lable).decode('utf-8').strip())
print('**********************')
# 获取所有的 li 标签下的所有 class 属性值
result = html.xpath('//li/@class')
for lable in result:
print(lable)
print('**********************')
# 获取所有的 li 标签下 href 为 ccc.html 的 a 标签
result = html.xpath("//li/a[@href = 'ccc.html']")
for lable in result:
print(etree.tostring(lable).decode('utf-8'))
print('**********************')
# 获取所有的 li 标签下的 span 标签
result = html.xpath("//li//span")
for lable in result:
print(etree.tostring(lable).decode('utf-8'))
print('**********************')
# 获取所有的 li 标签下的 a 标签中的所有 href
result = html.xpath("//li/a//@href")
for lable in result:
print(lable)
print('**********************')
# 获取最后一个的 li 标签下的 a 标签中的 href
result = html.xpath("//li[last()]/a/@href")
for lable in result:
print(lable)
print('**********************')
# 获取倒数第二个 li 元素中的内容
result = html.xpath("//li[last()-1]/a")
for lable in result:
print(lable.text)
print('**********************')
# 获取倒数第二个 li 元素中的内容
result = html.xpath("//li[last()-1]/a/text()")
print(result)
结果为:
<li class="a"><a href="aaa.html">aaa</a></li>
<li class="b"><a href="bbb.html">bbb</a></li>
<li class="c"><a href="ccc.html">ccc</a></li>
<li class="b"><a href="ddd.html">ddd</a></li>
<li class="a"><a href="eee.html">eee</a></li>
**********************
a
b
c
b
a
**********************
<a href="ccc.html">ccc</a>
**********************
**********************
aaa.html
bbb.html
ccc.html
ddd.html
eee.html
**********************
eee.html
**********************
ddd
**********************
['ddd']
需要注意的是 / 和 // 的区别使用。
实例
import requests
from lxml import etree
MAX_PAGES = 50
MAJOR_URL = "https://www.ygdy8.netl"
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36',
'Referer':'https://www.ygdy8.net/html/gndy/dyzz/index.html'
}
film_url_xpath = "//div[@class = 'co_content8']//tr/td[2]//a/@href"
film_text_xpath = "//div[@class = 'co_content8']//tr/td[2]//a/text()"
film_data_xpath = "//div[@class = 'co_content8']//tr/td[2]//font/text()"
film_detail_xpath = "//div[@class = 'co_content8']//tr/td[@colspan]//text()"
def index_url(index):
base_url = "https://www.ygdy8.net/html/gndy/dyzz/list_23_.html"
return base_url.replace('.html',str(index)+'.html')
if __name__ == '__main__':
html = etree.parse('text.html')
with open('dyttfilm.txt','w',encoding='utf-8') as fp:
for i in range(MAX_PAGES):
url = index_url(i+1)
response = requests.get(url,headers=headers)
html = etree.HTML(response.content.decode('gb2312','ignore'))
film_url = html.xpath(film_url_xpath)
film_text = html.xpath(film_text_xpath)
film_data = html.xpath(film_data_xpath)
film_detail = html.xpath(film_detail_xpath)
for j in range(len(film_url)):
item = MAJOR_URL+film_url[j]+' '+film_text[j]+' '+film_data[j].split('\n')[0]+' '+film_detail[j]+'\n\n'
fp.write(item)
fp.close()
使用上边的程序可以爬取电影天堂页面的电影信息,这里只保存了前 50 页的电影信息,在第 97 页的时候,有一部电影不存在 film_detail 信息,如果提取的页面过多的时候,需要注意这部分信息。