二、解析

Xpath

1.初识Xpth

1.1什么是Xpath?

XPath (XML Path Language) 是一门在 XML 文档中查找信息的语言,可用来在 XML 文档中对元素和属性进行遍历。

XPath定位在爬虫和自动化测试中都比较常用,通过使用路径表达式来选取 XML 文档中的节点或者节点集,熟练掌握XPath可以极大提高提取数据的效率。

1.2Xpath语法

选取节点

表达式

描述

nodename

选取此节点的所有子节点。

/

从根节点选取(取子节点)。

//

从匹配选择的当前节点选择文档中的节点,而不考虑它们的位置(取子孙节点)。

.

选取当前节点。


选取当前节点的父节点。

@

选取属性。

路径表达式

结果

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 元素。

通配符

描述

*

匹配任何元素节点。

@*

匹配任何属性节点。

@*

匹配任何属性节点。

在下面的表格中,我们列出了一些路径表达式,以及这些表达式的结果:

路径表达式

结果

/bookstore/*

选取 bookstore 元素的所有子元素。

//*

选取文档中的所有元素。

//title[@*]

选取所有带有属性的 title 元素。

选取若干路径

通过在路径表达式中使用"|"运算符,可以选取若干个路径。

在下面的表格中,我们列出了一些路径表达式,以及这些表达式的结果:

路径表达式

结果

//book/title | //book/price

选取 book 元素的所有 title 和 price 元素。

//title | //price

选取文档中的所有 title 和 price 元素。

/bookstore/book/title | //price

选取属于 bookstore 元素的 book 元素的所有 title 元素,以及文档中所有的 price 元素。

1.3HTML的基本标签

标题:`<h1>、<h2>、<h3>、<h4>、<h5>、<h6>、<title>`

段落:<p>

链接:<a>

图像:<img>

样式:<style>

列表:`无序列表<ul>、有序列表<ol>、列表项<li>`

块:`<div>、<span>`

脚本:<script>

注释:<!--注释-->

2.Xpath的基本使用

2.1安装lxml库

在pycharm的设置–>项目:Python爬虫–>Python解释器 中进行安装。

2.2Xpath解析类型

(1)本地文件 etree.parse

(2)服务器响应的数据 etree.HTML(content)

2.2.1Xpath解析本地文件

本地文件

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8"/>
    <title>Title</title>
</head>
<body>
    <ul>
        <li id="l1" class="c1">北京</li>
        <li id="l2">上海</li>
        <li id="c3">深圳</li>
        <li id="c4">上海</li>
    </ul>
    <ul>
        <li>大连</li>
        <li>锦州</li>
        <li>沈阳</li>
    </ul>

</body>
</html>

解析的基本使用

from lxml import etree

# xpath解析本地文件
tree = etree.parse('17_解析_xpath的基本使用.html')

# tree.xpath('xpath路径')
# // 查找所有的子孙结点,不考虑层级关系
#//  找直接子节点

# 查找ul下面的li
# li_list = tree.xpath('//body/ul/li')

# 查找所有有id的属性的li标签
# text()获取标签中的内容
# li_list = tree.xpath('//ul/li[@id]/text()')

# 找到id为l1的li标签
# li_list = tree.xpath('//ul/li[@id="l1"]/text()')

# 查找到id为l1的li标签的class的属性值
# []中写标签的属性值,属性名前要有@,值用""括起来
# li = tree.xpath('//ul/li[@id="l1"]/@class')

# 查询id中包含l的li标签
# li_list = tree.xpath('//ul/li[contains(@id, "l")]/text()')

# 查询id的值以l开头的li标签
# li_list = tree.xpath('//ul/li[starts-with(@id, "l")]/text()')

# 查询id为l1和class为c1的标签
# li_list = tree.xpath('//ul/li[@id="l1" and @class="c1"]/text()')

# | (或)不能再标签之间用
li_list = tree.xpath('//ul/li[@id="l1"]/text() | //ul/li[@id="l2"]/text()')

print(li_list)
print(len(li_list))
2.2.2Xpath解析服务器响应数据

例一:以获取百度网站的“百度一下”为例

import urllib.request

url = 'https://www.baidu.com'

headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36'
}

reqeust = urllib.request.Request(url=url, headers=headers)

response = urllib.request.urlopen(request)

content = response.read().decode('utf-8')

from lxml import etree

# 解析服务器响应的数据
tree = etree.HTML(content)

# 获取想要的数据 xpath的返回值是一个列表类型的数据
result = tree.xpath('//input[@id="su"]/@value')[0]

print(result)

例二:利用解析下载站长素材的前十页图片

import urllib.request
form lxml import etree
# 需求 下载前十页的图片
# https://sc.chinaz.com/tupian/jianzhutupian.html
# https://sc.chinaz.com/tupian/jianzhutupian_2.html
# https://sc.chinaz.com/tupian/jianzhutupian_3.html

# 封装请求对象定制
def create_request(page):
    if (page == 1):	# 因为第一页的url和其他页的不同需要单独判断
        url = 'https://sc.chinaz.com/tupian/jianzhutupian.html'
    else:
        url = 'https://sc.chinaz.com/tupian/jianzhutupian_' + str(page) + '.html'
    
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36'
    }
    
    request = urllib.request.Request(url=url, headers=headers)
    
    return request

# 封装获取网页内容
def get_content(request):
    response = urllib.request.urlopen(request)
    content = response.read().decode('utf-8')
    
    return content

def down_load(content):
    # 下载图片	利用xpath找出图片名称以及图片的路径
    tree = etree.HTML(content)
    name_list = tree.xpath('/html/body/div[3]/div[2]/div//img/@alt')
    src_list = tree.xpath('/html/body/div[3]/div[2]/div/img/@data-original')
    
    for i in range(len(name_list)):
        name = name_list[i]
        src = src_list[i]
        url = 'https:' + src
        
        urllib.request.urlretrieve(url=url, filename='./loveImg/' + name + '.jpg')
        
if __name__ == '__main__':
    start_page = int(input('请输入起始页码:'))
    end_page = int(input('请输入结束页码:'))

    for page in range(start_page, end_page+1):
        # (1)请求对象的定制
        request = create_request(page)
        # (2)模拟向服务器发送数据
        content = get_content(request)
        # (3)下载
        down_load(content)

JsonPath

1.初识JsonPath

1.1关于JsonPath

JsonPath和JSON文档有关系,正如XPath之于XML文档一样,JsonPath为Json文档提供了解析能力,通过使用JsonPath,你可以方便的查找节点、获取想要的数据,JsonPath是Json版的XPath。

1.2JsonPath语法

(1)JsonPath语法要点:

  • $ 表示文档的根元素
  • @ 表示文档的当前元素
  • .node_name['node_name'] 匹配下级节点
  • [index] 检索数组中的元素
  • [start:end:step] 支持数组切片语法
  • * 作为通配符,匹配所有成员
  • .. 子递归通配符,匹配成员的所有子元素
  • (<expr>) 使用表达式
  • ?(<boolean expr>)进行数据筛选
    (2)JsonPath与Xpath的比较

XPath

JsonPath

说明

/

$

文档根元素

.

@

当前元素

/

.[]

匹配下级元素

..

N/A

匹配上级元素,JsonPath不支持此操作符

//

..

递归匹配所有子元素

*

*

通配符,匹配下级元素

@

N/A

匹配属性,JsonPath不支持此操作符

[]

[]

下标运算符,根据索引获取元素,XPath索引从1开始,JsonPath索引从0开始

`

`

[,]

N/A

[start:end:step]

数据切片操作,XPath不支持

[]

?()

过滤表达式

N/A

()

脚本表达式,使用底层脚本引擎,XPath不支持

()

N/A

分组,JsonPath不支持

注意:

  • JsonPath的索引从0开始计数
  • JsonPath中字符串使用单引号表示,例如:$.store.book[?(@.category=='reference')]中的'reference'

2.JsonPath的基本使用

2.1JsonPath解析本地文件

本地文件

{ "store": {
    "book": [
      { "category": "修真",
        "author": "六道",
        "title": "坏蛋是怎样练成的",
        "price": 8.95
      },
      { "category": "修真",
        "author": "天蚕土豆",
        "title": "斗破苍穹",
        "price": 12.99
      },
      { "category": "修真",
        "author": "唐家三少",
        "title": "斗罗大陆",
        "isbn": "0-553-21311-3",
        "price": 8.99
      },
      { "category": "修真",
        "author": "南派三叔",
        "title": "星辰变",
        "isbn": "0-395-19395-8",
        "price": 22.99
      }
    ],
    "bicycle": {
      "color": "黑色",
      "price": 19.95
    }
  }
}

代码示例

import urllib.request
import json
import jsonpath

#加载本地文件
obj = json.load(open('filename'))

# 获取书店下的所有作者
author_list = jsonpath.jsonpath(obj, '$.store.book[*].author')
print(author_list)

# 直接利用绝对路径获取所有作者
authors_list = jsonpath.jsonpath(obj, '$..author')
print(authors_list)

# 获取store下所有的元素
tag_list = jsonpath.jsonpath(obj, '$.store.*')
print(tag_list)

# 获取store下所有东西的价格
price_list = jsonpath.jsonpath(obj, '$.store..price')
print(price_list)

注意:

  • json.load()参数是对象
  • json.loads()参数是字符串

2.2JaonPath解析服务器响应文件

以解析淘票票为例

import urllib.request
import json

url = 'https://dianying.taobao.com/cityAction.json?activityId&_ksTS=1681105103837_108&jsoncallback=jsonp109&action=cityAction&n_s=new&event_submit_doGetAllRegion=true'

# 经测试只需要referer即可
headers = {
    'referer': 'https://dianying.taobao.com/?spm=a1z21.3046609.city.1.7db2112axDN5Jc&n_s=new&city=110100',
}

request = urllib.request.Request(url=url, headers=headers)

response = urllib.request.urlopen(reqeust)

content = response.read().decode('utf-8')

#利用分隔符分割,返回的是数组,取第二个元素,即下标为1
content = content.split('(')[1].split(')')[0]

#写入本地文件
with open('解析_jsonpath解析淘票票.json', 'w', encoding='utf-8') as fp:
    fp.write(content)

obj = json.load(open('解析_jsonpath解析淘票票.json', 'r', encoding='utf-8'))

city_list = jsonpath.jsonpath(obj, '$..regionName')

print(city_list)

关于本例url如何获取的问题

f12,刷新主页,清空全部nerwork栏内容,然后鼠标点击地名会出现接口,接口中有url,为json数据内容。

注意:

  • content.split('(')'('为分割符进行分割,并以列表 形式返回

Bs4

1.初识Bs4

1.1什么是Bs4

BS4全称是Beatiful Soup,它提供一些简单的、python式的函数用来处理导航、搜索、修改分析树等功能。它是一个工具箱,通过解析文档为tiful Soup自动将输入文档转换为Unicode编码,输出文档转换为utf-8编码。

1.2Bs4语法

在 BS4 中,通过标签名和标签属性可以提取出想要的内容

eg:

获取整个p标签的html代码:soup.p

获取p标签:soup.p.b

获取p标签内容:soup.p.text

2.Bs4的基本使用

2.1Bs4解析本地文件

本地文件

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <div>

        <ul>
            <li id="l1">张三</li>
            <li id="l2">李四</li>
            <li>王五</li>
            <a href="" id="" class="a1">尚硅谷</a>
        </ul>
    </div>

    <a href="" title="a2">百度</a>

    <div id="d1">
        <span>
            哈哈哈
        </span>
    </div>

    <p id="p1" class="p1">呵呵呵</p>
</body>
</html>

解析本地文件

from bs4 import BeautifulSoup

# 默认打开文件的格式是gbk
soup = BeautifulSoup(open('解析_bs4的基本使用.html', encoding='utf-8'), 'lxml')

# 根据标签名查找节点
# 找到的是第一个符合条件的数据
print(soup.a)
print(soup.a.attrs)	# attrs 获取标签的属性和属性值

# bs4的一些函数
# (1)find
# 返回第一个符合条件的数据
print(soup.find('a'))
# 根据title的值找到对应的标签对象
print(soup.find('a', title="a2"))
# 根据class的值找到对应的标签对象,class需要加下划线
print(soup.find('a', class_="a1"))

# (2)find_all 返回列表,并返回所有的a标签
print(soup.find_all('a'))

# 如果想获取的是多个标签的数据,那么需要在参数中添加列表的数据(参数的数据类型是列表)
print(soup.find_all('['a', 'span']'))
# limit的作用,查找前几个数据
print(soup.find_all('li', limit=2))

# (3) select 推荐使用
# select方法返回的是一个列表,并且返回多个数据
print(soup.select('a'))
# 可以通过 . 代表class,我们把这种操作叫做类选择器
print(soup.select('.a1'))
# 可以通过 # 代表id
print(soup.select('#l1')) 

# 属性选择器--通过属性来寻找对应的标签
# 查找li标签中有id的标签
print(soup.select('li[id]'))
# 查找li标签中id为l2的标签
print(soup.select('li[id="l2"]'))

# 层级选择器
# 后代选择器
# 找到的是div下面的li
print(soup.select('div li'))

# 子代选择器
# 某标签的第一级子标签
# 注意:很多的计算机编程语言中,如果不加空格不会输出内容,但在bs4中,不会报错,会显示内容
print(soup.select('div > ul > li'))

# 找到a标签和li标签的所有对象
print(soup.select('a, li'))

# 节点信息
# 获取节点信息
obj = soup.select('#d1')[0]
# 如果标签对象中只有内容,那么string和get_text()都可以使用
# 如果标签对象中除了内容还有标签,那么string就获取不到数据,而get_text()可以获取数据
# 推荐使用 get_text()
print(obj.string)	
print(obj.get_text())

# 节点的属性
obj = soup.select('#p1')[0]
# name是标签的名字
print(obj.name)
# 将属性值作为一个字典返回
print(obj.attrs)

# 获取节点的属性
obj = soup.select('#p1')[0]

print(obj.attrs.get('class')) # 通过字典中的get()方法获取class的值
print(obj.get('class'))
print(obj['class'])

2.1Bs4解析服务器响应文件

以爬取星巴克数据为例

import urllib.request
from bs4 import BeautifulSoup

url = ''

response = urllib.request.urlopen(url)

content = response.read().decode('utf-8')

soup = BeautifulSoup(content, 'lxml')

# //ul[@class="grid padded-3 product"]//strong/text()
# name_list = soup.select('.grid padded-3 product') 错误的
name_list = soup.select('ul[class="grid padded-3 product"] strong')
# print(name_list)

for name in name_list:
    # print(name.string)
    print(name.get_text())