Python爬虫实例(4)--xpath选择器

系列文章目录

本节目标:使用xpath选取器得到所有章节的链接。

本节内容:xpath解析HTML
本节技术点:lxml,xpath

本节阅读需要(20)min。
本节实操需要(20)min。



前言

xpath作为一种HTML专用的解析语法。可以快速的定位内容。
相当于HTML的正则表达式。

和前一节的bs4爬取信息的思路是差不多的,但是方式和形式上看上去有不小的差异。


一、构建文档树

使用时先安装 lxml 包

pip install lxml
from lxml import etree


# 读取外部文件 index.html
html = etree.parse('./index.html')
result = etree.tostring(html, pretty_print=True)    #pretty_print=True 会格式化输出
print(result)

parse读取。
tostring将etree的文档节点对象转化为文本字符串。

不过一般上游不是本地的HTML,而是response返回的text。
html  = etree.HTML(html_text)
result = etree.tostring(html,encoding="gbk")
print(str(result,'gbk')) # 相当于decode

根据我们的实例,需要这么处理,不然又回乱码了…

很多教程是utf-8.但是对于我们的实例是不对的.因为其他人的教程都是内部的格式字符串,自然是utf-8

但是更多的中文网页的情形却不是,尤其是文本含量比较多的网站.

二、xpath语法

xpath的语法也是递归的.也就是说返回的还是一个可以解析的文本节点对象.
这与前面的bs4和find是一个逻辑.

r_list = parse_html.xpath('xpath表达式')

返回的是一个对象的列表.

语法元素

表达式	描述
nodename	选取此节点的所有子节点
/	从当前节点选取直接子节点
//	从当前节点选取子孙节点
.	选取当前节点
..	选取当前节点的父节点
@	选取属性
*	通配符,选择所有元素节点与元素名
@*	选取所有属性
[@attrib]	选取具有给定属性的所有元素
[@attrib='value']	选取给定属性具有给定值的所有元素
[tag]	选取所有具有指定元素的直接子节点
[tag='text']	选取所有具有指定元素并且文本内容是text节点


contains(string1,string2)
starts-with(string1,string2)
ends-with(string1,string2) #不支持
upper-case(string) #不支持
text()
last()
position()
node()

以上的这些都是通过内容匹配。
与HTML和前端有一点不一样的是,标签里面所有的都会作为属性比如@style,@class
语法和css的还是比较像的.
但是写到一起就看上去很不一样了…

内容匹配高级部分

要选择 style属性值 包含 color 字符串的 页面元素 ,可以这样 //*[contains(@style,'color')]

要选择 style属性值 以 color 字符串 开头 的 页面元素 ,可以这样 //*[starts-with(@style,'color')]

要选择 style属性值 以 某个 字符串 结尾 的 页面元素 ,大家可以推测是 //*[ends-with(@style,'color')]

根据位置匹配

xpath使用的时候永远是从后往前,只要能匹配到,或者大致匹配到,就行。不需要完整的xpath表达式,
因为容易造成空匹配

//div/p[last()-2]
//*[@class='multi_choice']/*[position()<=3]
//tr[position()=8 or position()=9]/td/span[@class="J_zhaiyao"]/text()
# xpath也可以选择 后续 兄弟节点,用这样的语法 following-sibling::
//*[@class='single_choice']/following-sibling::*
# xpath还可以选择 前面的 兄弟节点,用这样的语法 preceding-sibling::
//*[@class='single_choice']/preceding-sibling::*

last()和position()为xpath特有的固定常数,就像awk的NR,NF这些。

实例讲解

我来随便写几个实例,由简单到复杂讲解一下xpath的用法.

html.xpath('//li')   #获取所有子孙节点的li节点
html.xpath('//li/a')   #获取所有子孙节点的li节点的直接子节点a
html.xpath('//book|//cd') #代表这两个的集合

# 属性限定,不限于class
html.xpath('//li/a[@href]')   #获取所有子孙节点的li节点的直接子节点a,但href要求含有href这个属性
html.xpath('//li/a[@href="link2.html"]')   #获取所有子孙节点的li节点的直接子节点a,但href要求为指定值,要求完全匹配
# 多个属性值类如class="aaa bbb",使用contains(),部分匹配
html.xpath('//li/a[contains(@class,"aaa")]')
# 多个属性筛选器用and或者or逻辑运算。
result1=html.xpath('//li[contains(@class,"aaa") and @name="fore"]/a/text()')
# 含有整数值的筛选器
html.xpath('//li/a[@num>10]') 
html.xpath('//li/a[@num1>10 and @num2]') 

# 获取class的值,不限于class
html.xpath('//li/a[@href="link2.html"]/@class')
# 文本获取
html.xpath('//li/a[@href="link2.html"]/text()')

注意:
如果是直接的子元素html.xpath(‘a’) ,无需再加/.或者写成xpath(‘./a’)。
html.xpath(‘/a’) 指的是孙子节点a。

所以大致分为三个步骤

  1. 用//大步长跳转,/小步长跳转定位到tag
  2. 使用属性限定得到目标地元素集合
  3. 提取文本信息。

实战

我们需要得到每个章节的链接。
其实就是在昨天的基础上更进一步。
也可以用CSS选择器完成。

bs4版本

dt_nu = 0
href_list = []
for title in raw_titles:

    if not title.find('a') :
        print("dt")
        dt_nu +=1
    
    if dt_nu >=2 and title.find('a') :
        href_str = title.find('a')["href"] # 用CSS选择器完成的
        href_list.append(href_str)
with open("href.txt","w",encoding='utf-8') as HREF:
    for title in href_list:
        if title:
            HREF.write(title+"\n")

注意lxml和bs4数据类型不一样,所以互不兼容的。

xpath版本

html  = etree.HTML(html_text)
# result = etree.tostring(html,encoding="gbk")
# print(result)
# print(str(result,'gbk'))

raw_list = html.xpath('//div[@class="listmain"]/dl/*')
print(len(raw_list))

dt_nu = 0
href_list = []
# print(raw_list[1].xpath('./a'))
for title in raw_list:

    if not title.xpath('./a') :
        print("dt")
        dt_nu +=1
    
    if dt_nu >=2 and title.xpath('./a') :
        href_str = title.xpath('./a/@href')[0] # 哪怕只有一个返回的也是列表
        href_list.append(href_str)

with open("href.txt","w",encoding='utf-8') as HREF:
    for title in href_list:
        if title:
            HREF.write(title+"\n")

但是我们发现href只是一部分。
如下:

/1_1852/835564.html
/1_1852/835565.html
/1_1852/835566.html
/1_1852/835567.html
/1_1852/835568.html
/1_1852/835569.html
/1_1852/835570.html
/1_1852/835571.html

显然不是完整的url。
所以我们需要url链接

raw_list = html.xpath('//div[@class="listmain"]/dl/*')
print(len(raw_list))

base_url = 'https://www.zhhbq.com/' # 前缀
dt_nu = 0
href_list = []
# print(raw_list[1].xpath('./a'))
for title in raw_list:

    if not title.xpath('./a') :
        print("dt")
        dt_nu +=1
    
    if dt_nu >=2 and title.xpath('./a') :
        href_str = title.xpath('./a/@href')[0]
        href_str = base_url+href_str[1:] # 去除一个重复的/
        href_list.append(href_str)

with open("href.txt","w",encoding='utf-8') as HREF:
    for title in href_list:
        if title:
            HREF.write(title+"\n")
https://www.zhhbqg.com/1_1852/1_1852/835564.html
https://www.zhhbqg.com/1_1852/1_1852/835565.html
https://www.zhhbqg.com/1_1852/1_1852/835566.html
https://www.zhhbqg.com/1_1852/1_1852/835567.html

well done成功!!!


总结

注意xpath的语法。
使用*匹配的时候需要注意,有没有干扰项?
能不能用属性去除?
不能的话后面还需要自己写逻辑过滤。

注意,是先看实际的网页是什么url。然后根据自己爬取到的信息自动批量化拼接的。
不是用爬到的信息瞎拼接的。规则不是爬虫工程师定的,是后端路由规则决定的。

延伸阅读:

lxml延伸阅读

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

演技拉满的白马

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值