您当前的位置: 首页 >  Python

快乐很重要的汪

暂无认证

  • 2浏览

    0关注

    36博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

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

快乐很重要的汪 发布时间:2022-09-02 23:30:39 ,浏览量:2

系列文章目录

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

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

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

文章目录
  • 系列文章目录
  • 前言
  • 一、构建文档树
  • 二、xpath语法
    • 语法元素
      • 内容匹配高级部分
      • 根据位置匹配
    • 实例讲解
  • 实战
    • bs4版本
    • xpath版本
  • 总结

前言

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()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延伸阅读

关注
打赏
1663165558
查看更多评论
立即登录/注册

微信扫码登录

0.0356s