第十二阶段 -- 爬虫02:【request;数据提取(正则,Beautiful Soup,xpath)】

原文链接:https://mp.csdn.net/mdeditor/97143502#

文章目录

1. URLError

  • 首先解释下URLError可能产生的原因:

    • 网络无连接,即本机无法上网

    • 连接不到特定的服务器

    • 服务器不存在

  • 在代码中,我们需要用try-except语句来包围并捕获相应的异常,代码如下:

from urllib.request import Request, urlopen
from fake_useragent import UserAgent
from urllib.error import URLError

url = 'http://www.sxt.cn/index/login/login12353wfeds.html' # 服务器有,资源没有
url = 'http://www.sxt12412412.cn/index/login/login12353wfeds.html'
headers = {'User-Agent': UserAgent().chrome}
try:
    req = Request(url, headers=headers)
    resp = urlopen(req)
    info = resp.read().decode()
    print(info)
except URLError as e:
    if len(e.args) != 0:
        print('地址获取错误!')
    else:
        print(e.code)
print('爬取已完成')

  • 关于 debug 模式的技巧:

第十二阶段 -- 爬虫02:【request;数据提取(正则,Beautiful Soup,xpath)】

  • 我们利用了 urlopen方法访问了一个不存在的网址,运行结果如下:
[Errno 11004] getaddrinfo failed

2. request 库的用法

2.1. 基本介绍

  1. 介绍:

    对了解一些爬虫的基本理念,掌握爬虫爬取的流程有所帮助。入门之后,我们就需要学习一些更加高级的内容和工具来方便我们的爬取。那么这一节来简单介绍一下 requests 库的基本用法。

  2. 安装

    利用 pip 安装:

pip install requests
  1. 基本请求
req = requests.get("http://www.baidu.com")
req = requests.post("http://www.baidu.com")
req = requests.put("http://www.baidu.com")
req = requests.delete("http://www.baidu.com")
req = requests.head("http://www.baidu.com")
req = requests.options("http://www.baidu.com")

2.2. get 请求

  • 参数是字典,我们也可以传递 json 类型的参数:
  1. get 的使用01:
import requests
from fake_useragent import UserAgent

url = 'http://www.baidu.com'
headers = {'User-Agent': UserAgent().chrome}
resp = requests.get(url, headers=headers)
resp.encoding='utf-8'
print(resp.text)

  1. get 的使用02:
import requests
from fake_useragent import UserAgent

url = 'http://www.baidu.com/s?'
params = {
    'wd': '黑马程序员'
}
headers = {'User-Agent': UserAgent().chrome}
resp = requests.get(url, headers=headers, params=params)
resp.encoding = 'utf-8'
print(resp.text)

2.3. post 请求

  1. 参数是字典,我们也可以传递 json 类型的参数:
  2. 代码示例01:
import requests
from fake_useragent import UserAgent

url = 'http://www.sxt.cn/index/login/login.html'
args = {
    'user': '17703181473',
    'password': '123456'
}
headers={'User-Agent':UserAgent().chrome}
resp = requests.post(url,headers=headers,data=args)
print(resp.text)

  1. 代码示例02:
import requests
from fake_useragent import UserAgent

# 登录
login_url = 'https://www.kuaidaili.com/login/'

headers = {'User-Agent': UserAgent().chrome}
data = {
    'username': '398707160@qq.com',
    'passwd': '123456abc'
}

resp = requests.post(login_url, headers=headers, data=data)
print(resp.text)


2.4. 自定义请求头部

  • 伪装请求头部是采集时经常用的,我们可以用这个方法来隐藏:
headers = {'User-Agent': 'python'}
r = requests.get('http://www.zhidaow.com', headers = headers)
print(r.request.headers['User-Agent'])

2.5. 设置超时时间

  • 可以通过timeout属性设置超时时间,一旦超过这个时间还没获得响应内容,就会提示错误
requests.get('http://github.com', timeout=0.001)

2.6. 代理访问

  1. 采集时为避免被封IP,经常会使用代理。requests也有相应的proxies属性:
import requests

proxies = {
  "http": "http://10.10.1.10:3128",
  "https": "https://10.10.1.10:1080",
}

requests.get("http://www.zhidaow.com", proxies=proxies)
  1. 如果代理需要账户和密码,则需这样:
proxies = {
    "http": "http://user:pass@10.10.1.10:3128/",
}
  1. 代码示例:
import requests
from fake_useragent import UserAgent

url = 'http://httpbin.org/get'
headers = {'User-Agent': UserAgent().chrome}

# proxy = {
#     'type': 'type://ip:port',
#     'type': 'type://username:password@ip:port'
# }
proxy = {
    'http':'http://117.191.11.102:8080'
    #'http': 'http://398707160:j8inhg2g@58.87.79.136:16817'

}

resp = requests.get(url, headers=headers, proxies=proxy)
print(resp.text)

2.7. session 自动保存 cookies

  1. seesion 的意思是保持一个会话,比如:登陆后继续操作(记录身份信息)而requests 是单次请求的请求,身份信息不会被记录。
# 创建一个session对象 
s = requests.Session()
# 用session对象发出get请求,设置cookies 
s.get('http://httpbin.org/cookies/set/sessioncookie/123456789')
  1. 代码示例:
import requests
from fake_useragent import UserAgent

# 登录
login_url = 'http://www.sxt.cn/index/login/login'

# 个人信息
info_url = 'http://www.sxt.cn/index/user.html'
headers = {'User-Agent': UserAgent().chrome}
data = {
    'user': '17703181473',
    'password': '123456'
}
# 开启session对象,把cookie保存在session中
session = requests.Session()
resp = session.post(login_url, headers=headers, data=data)
# 获取响应内容(以字符串)
print(resp.text)

info_resp = session.get(info_url, headers=headers)

print(info_resp.text)

2.8. ssl 验证

# 禁用安全请求警告
requests.packages.urllib3.disable_warnings()

resp = requests.get(url, verify=False, headers=headers)

2.9. request 获取相应信息

代码 含义
resp.json() 获取响应内容(以 json 字符串
resp.text 获取响应内容 (以字符串)
resp.content 获取响应内容(以字节的方式
resp.headers 获取响应头内容
resp.url 获取访问地址
resp.encoding 获取网页编码
resp.request.headers 请求头内容
resp.cookie 获取cookie
resp.state_code 响应状态码

3. 数据的提取

3.1. 正则表达式 re (逼格最高;速度最快)

1. 提取数据

  1. 在前面我们已经搞定了怎样获取页面的内容,不过还差一步,这么多杂乱的代码夹杂文字我们怎样把它提取出来整理呢?下面就开始介绍一个十分强大的工具,正则表达式!

    正则表达式是对字符串操作的一种逻辑公式,就是用事先定义好的一些特定字符、及这些特定字符的组合,组成一个“规则字符串”,这个“规则字符串”用来表达对字符串的一种过滤逻辑。

    正则表达式是用来匹配字符串非常强大的工具,在其他编程语言中同样有正则表达式的概念,Python同样不例外,利用了正则表达式,我们想要从返回的页面内容提取出我们想要的内容就易如反掌了

  2. 规则

模式 描述
$ 匹配字符串的末尾
. 匹配任意一个字符,除了换行符,当re.DOTALL标记被指定时,则可以匹配包括换行符的任意字符
[…] 用来表示一组字符,单独列出:[amk] 匹配 ‘a’,‘m’或’k’
[^…] 不在[]中的字符:abc 匹配除了a,b,c之外的字符
re* 匹配0个或多个的表达式
^ 匹配字符串的开头
re+ 匹配1个或多个的表达式
re? 匹配0个或1个由前面的正则表达式定义的片段,非贪婪方式
re{ n}
re{ n,} 精确匹配n个前面表达式
re{ n,m} 匹配 n 到 m 次由前面的正则表达式定义的片段,贪婪方式
a b
(re) G匹配括号内的表达式,也表示一个组
(?-imx) 正则表达式关闭 i, m, 或 x 可选标志。只影响括号中的区域
(?imx) 正则表达式包含三种可选标志:i, m, 或 x 。只影响括号中的区域
(?: re) 类似 (…), 但是不表示一个组
(?imx: re) 在括号中使用i, m, 或 x 可选标志
(?-imx: re) 在括号中不使用i, m, 或 x 可选标志
(?#…) 注释
(?= re) 前向肯定界定符。如果所含正则表达式,以 … 表示,在当前位置成功匹配时成功,否则失败。但一旦所含表达式已经尝试,匹配引擎根本没有提高;模式的剩余部分还要尝试界定符的右边。
(?! re) 前向否定界定符。与肯定界定符相反;当所含表达式不能在字符串当前位置匹配时成功
(?> re) 匹配的独立模式,省去回溯
\w 匹配字母数字及下划线
\W 匹配非字母数字及下划线
\s 匹配任意空白字符,等价于 [\t\n\r\f].
\S 匹配任意非空字符
\d 匹配任意数字,等价于 [0-9]
\D 匹配任意非数字
\A 匹配字符串开始
\Z 匹配字符串结束,如果是存在换行,只匹配到换行前的结束字符串。c
\z 匹配字符串结束
\G 匹配最后匹配完成的位置
\b 匹配一个单词边界,也就是指单词和空格间的位置。例如, ‘er\b’ 可以匹配"never" 中的 ‘er’,但不能匹配 “verb” 中的 ‘er’
\B 匹配非单词边界。‘er\B’ 能匹配 “verb” 中的 ‘er’,但不能匹配 “never” 中的 ‘er’
\n, \t, 等. 匹配一个换行符。匹配一个制表符。等
\1…\9 匹配第n个分组的内容
\10 匹配第n个分组的内容,如果它经匹配。否则指的是八进制字符码的表达式
[\u4e00-\u9fa5] 中文

2. 正则表达式相关注解

  1. 数量词的贪婪模式非贪婪模式

    正则表达式通常用于在文本中查找匹配的字符串
    Python里数量词默认是贪婪的(在少数语言里也可能是默认非贪婪),总是尝试匹配尽可能多的字符;非贪婪的则相反,总是尝试匹配尽可能少的字符

    • 例如:正则表达式 ‘ab*’ 如果用于查找”abbbc”,将找到”abbb”。而如果使用非贪婪的数量词”ab?”,将找到”ab”。
  2. 常用方法

    1. re.match

      • re.match 尝试从字符串的起始位置匹配一个模式,如果不是起始位置匹配成功的话,match()就返回none
      • 函数语法:
        re.match(pattern, string, flags=0)
    2. re.search

      • re.search 扫描整个字符串并返回第一个成功的匹配。
      • 函数语法:
        re.search(pattern, string, flags=0)
    3. re.sub

      • re.sub 替换字符串

      • 语法:

        re.sub(pattern,replace,string)

    4. re.findall

      • re.findall 查找全部

      • 语法:

        re.findall(pattern,string,flags=0)

  3. 正则表达式修饰符 - 可选标志

    正则表达式可以包含一些可选标志修饰符来控制匹配的模式。修饰符被指定为一个可选的标志。多个标志可以通过按位 OR(|) 它们来指定。如 re.I | re.M 被设置成 I 和 M 标志:

修饰符 描述
re.I 使匹配对大小写不敏感
re.L 做本地化识别(locale-aware)匹配
re.M
re.S 使 . 匹配包括换行在内的所有字符
re.U 根据Unicode字符集解析字符。这个标志影响 \w, \W, \b, \B
re.X 该标志通过给予你更灵活的格式以便你将正则表达式写得更易于理解
  • 代码示例:
import re

str1 = 'I study Python3.6 everday!'

				############ match ############
print('-' * 30, 'match()', '-' * 30)  
# match 从左到右,依次匹配(前面的也要匹配上),若匹配不到直接返回None

# m1 = re.match(r'I', str1)
# m1 = re.match(r'[I]', str1)
# m1 = re.match(r'\bI', str1)
# m1 = re.match(r'\w', str1)
# m1 = re.match(r'\S', str1) 
# m1 = re.match(r'(I)', str1)
# m1 = re.match(r'.', str1)
# m1 = re.match(r'\D', str1)
m1 = re.match(r'\w\s(study)', str1)
print(m1.group(1))

				############ search ############
print('-' * 30, 'search()', '-' * 30)  
# 从左到右,全部扫描,找到第一个后,返回结果,
s1 = re.search(r'study', str1)
s1 = re.search(r'y', str1)
print(s1.group())

				############ findall ############
print('-' * 30, 'findall()', '-' * 30)
f1 = re.findall(r'y', str1)
f1 = re.findall(r'Python3.6', str1)
f1 = re.findall(r'P\w*.\d', str1)
print(f1)

				############ sub ############
print('-' * 30, 'sub()', '-' * 30)
su1 = re.sub(r'everday', 'Everday', str1)
su1 = re.sub(r'ev.+', 'Everday', str1)
print(su1)

print('-' * 30, 'test()', '-' * 30)
str2 = '<span><a href="http://www.bjstx.com">尚硅谷sxt</a></span>'

# t1 = re.findall(r'[\u4e00-\u9fa5]+', str2)
# t1 = re.findall(r'>([\u4e00-\u9fa5]+)<', str2)
# t1 = re.findall(r'>(\S+?)<', str2)
t1 = re.findall(r'<a href=".*">(.+)</a>', str2)
t1 = re.findall(r'<a href="(.*)">.+</a>', str2)
print(t1)
t2 = re.sub(r'span', 'div', str2)
t2 = re.sub(r'<span>(.+)</span>', r'<div>\1</div>', str2)
print(t2)

  1. 练习01:爬取糗事百科的前三页内容,只爬取段子的内容
import requests
from fake_useragent import UserAgent
import re

with open('duanzi.txt', 'w', encoding='utf-8') as f:
    for i in range(1, 4):
        url = 'https://www.qiushibaike.com/text/page/{}/'.format(i)
        headers = {'User-Agent': UserAgent().chrome}
        resp = requests.get(url, headers=headers)
        html = resp.text
        infos = re.findall(r'<div class="content">\s<span>\s+(.+)', html)
        for info in infos:
            f.write('-' * 30 + '\n')
            f.write(info.replace(r'<br/>','\n'))
            f.write('\n' + '-' * 30 + '\n')

  1. 练习02:从盗墓笔记中爬取每个章节的内容
import requests
from fake_useragent import UserAgent
import re

url = 'http://www.jueshitangmen.info/daomubiji'
headers = {'User-Agent': UserAgent().chrome}
resp = requests.get(url, headers=headers)

all_url = re.findall(r'<a href="(.+)" rel',re.findall(r'<h2>盗墓笔记1</h2>\s+<ul>([\s\S]+?)</ul>', resp.text)[0])
print(all_url)
for info_url in all_url:
    info_resp = requests.get(info_url, headers=headers)

    # info = re.findall(r'<div style="padding: 40px 0px 0px 0px; width: 336px; float: right;"> </div><br>\s+([\d\D]+)<p align="center">',info_resp.text)
    info = re.findall(r'<p>(.+)</p>', re.findall(
        r'<div style="padding: 40px 0px 0px 0px; width: 336px; float: right;"> </div><br>\s+([\d\D]+)<p align="center">',
        info_resp.text)[0])

    print('\n'.join(info))

3.2. Beautiful Soup

1. 简介、安装、四大种类

  1. Beautiful Soup提供一些简单的、python式的函数用来处理导航、搜索、修改分析树等功能。它是一个工具箱,通过解析文档为用户提供需要抓取的数据,因为简单,所以不需要多少代码就可以写出一个完整的应用程序。

    Beautiful Soup自动将输入文档转换为Unicode编码,输出文档转换为utf-8编码。你不需要考虑编码方式,除非文档没有指定一个编码方式,这时,Beautiful Soup就不能自动识别编码方式了。然后,你仅仅需要说明一下原始编码方式就可以了。

    Beautiful Soup已成为和lxml、html6lib一样出色的python解释器,为用户灵活地提供不同的解析策略或强劲的速度

  2. 安装:Beautiful Soup 3 目前已经停止开发,推荐在现在的项目中使用 Beautiful Soup 4,不过它已经被移植到 BS4 了,也就是说导入时我们需要 import bs4。

    pip install beautifulsoup4
    pip install lxml
    

    Beautiful Soup支持Python标准库中的HTML解析器,还支持一些第三方的解析器,如果我们不安装它,则 Python 会使用 Python默认的解析器,lxml 解析器更加强大,速度更快,推荐安装

解析器 使用方法 优势 劣势
Python标准库 BeautifulSoup(markup, “html.parser”) 1. Python的内置标准库 2. 执行速度适中 3.文档容错能力强 Python 2.7.3 or 3.2.2)前 的版本中文档容错能力差
lxml HTML 解析器 BeautifulSoup(markup, “lxml”) 1. 速度快 2.文档容错能力强 需要安装C语言库
lxml XML 解析器 BeautifulSoup(markup, [“lxml”, “xml”]) BeautifulSoup(markup, “xml”) 1. 速度快 2.唯一支持XML的解析器 3.需要安装C语言库
html5lib BeautifulSoup(markup, “html5lib”) 1. 最好的容错性 2.以浏览器的方式解析文档 3.生成HTML5格式的文档 4.速度慢 不依赖外部扩展
  1. 创建 Beautiful Soup 对象

    from bs4 import BeautifulSoup
    bs = BeautifulSoup(html,"lxml")
    
  2. 四大对象种类:

    Beautiful Soup 将复杂HTML文档转换成一个复杂的树形结构,每个节点都是Python对象,所有对象可以归纳为4种:

    • Tag

    • NavigableString

    • BeautifulSoup(不常用)

    • Comment(不常用)

2. Tag

  • 通俗点讲就是 HTML 中的一个个标签;例如:<div> <title>

  • 使用方式:

# 以以下代码为例子
<title id='title'>尚学堂</title>
<div class='info' float='left'>Welcome to SXT</div>
<div class='info' float='right'>
    <span>Good Good Study</span>
    <a href='www.bjsxt.cn'></a>
    <strong><!--没用--></strong>
</div>
  1. 获取标签
# 以lxml方式解析
soup = BeautifulSoup(info, 'lxml')
print(soup.title)
# <title>尚学堂</title>

  • 注意:相同的标签只能获取第一个符合要求的标签
  1. 获取属性
# 获取所有属性
print(soup.title.attrs)
# class='info' float='left'

# 获取单个属性的值
print(soup.div.get('class'))
print(soup.div['class'])
print(soup.a['href'])
# info

3. NavigableString 获取内容

print(soup.title.string)
print(soup.title.text)
#尚学堂

4. BeautifulSoup

  • BeautifulSoup 对象表示的是一个文档的全部内容,大部分时候,可以把它当作 Tag 对象,它支持遍历文档树和搜索文档树中描述的大部分的方法。

    因为 BeautifulSoup 对象并不是真正的HTML或XML的tag,所以它没有name和attribute 属性。但有时查看它的 .name 属性是很方便的,所以 BeautifulSoup 对象包含了一个值为 “[document]” 的特殊属性 .name。

print(soup.name)
print(soup.head.name)
# [document]
# head

5. Comment

  • Comment 对象是一个特殊类型的 NavigableString 对象,其实输出的内容仍然不包括注释符号,但是如果不好好处理它,可能会对我们的文本处理造成意想不到的麻烦。
if type(soup.strong.string) == Comment:
    print(soup.strong.prettify())
else:
    print(soup.strong.string)

6. 搜索文档树

  • Beautiful Soup 定义了很多搜索方法,这里着重介绍2个:find()find_all() 。其它方法的参数和用法类似,请同学们举一反三

  • 过滤器:

    介绍 find_all() 方法前,先介绍一下过滤器的类型 ,这些过滤器贯穿整个搜索的API.过滤器可以被用在tag的name中,节点的属性中,字符串中或他们的混合中

  1. 字符串

最简单的过滤器是字符串.在搜索方法中传入一个字符串参数,Beautiful Soup会查找与字符串完整匹配的内容,下面的例子用于查找文档中所有的

标签

# 返回所有的div标签
print(soup.find_all('div'))

如果传入字节码参数,Beautiful Soup会当作UTF-8编码,可以传入一段Unicode 编码来避免Beautiful Soup解析编码出错

  1. 正则表达式

如果传入正则表达式作为参数,Beautiful Soup会通过正则表达式的 match() 来匹配内容。

# 返回所有的div标签
print (soup.find_all(re.compile("^div")))
  1. 列表

如果传入列表参数,Beautiful Soup会将与列表中任一元素匹配的内容返回。

# 返回所有匹配到的span,a标签
print(soup.find_all(['span','a']))
  1. keyword

如果一个指定名字的参数不是搜索内置的参数名,搜索时会把该参数当作指定名字tag的属性来搜索,如果包含一个名字为 id 的参数,Beautiful Soup会搜索每个tag的”id”属性。

#返回id为welcom的标签
print(soup.find_all(id='welcom'))
  1. True

True 可以匹配任何值,下面代码查找到所有的tag,但是不会返回字符串节点。

  1. 按照 CSS 搜索

按照CSS类名搜索tag的功能非常实用,但标识CSS类名的关键字 class 在Python中是保留字,使用 class 做参数会导致语法错误,从Beautiful Soup的4.1.1版本开始,可以通过 class_ 参数搜索有指定CSS类名的tag。

# 返回class等于info的div
print(soup.find_all('div',class_='info'))
  1. 按照属性的搜索
soup.find_all("div", attrs={"class": "info"})

7. CSS 选择器(扩展)

  • soup.select(参数):
表达式 说明
tag 选择指定标签
* 选择所有节点
#id 选择id为container的节点
.class 选取所有class包含container的节点
li a 选取所有li下的所有a节点
ul + p (兄弟)选择ul后面的第一个p元素
div#id > ul (父子)选取id为id的div的第一个ul子元素
table ~ div 选取与table相邻的所有div元素
a[title] 选取所有有title属性的a元素
a[class=”title”] 选取所有class属性为title值的a
a[href*=”sxt”] 选取所有href属性包含sxt的a元素
a[href^=”http”] 选取所有href属性值以http开头的a元素
a[href$=”.png”] 选取所有href属性值以.png结尾的a元素
input[type=“redio”]:checked 选取选中的hobby的元素

8. 代码示例

# pip install bs4
# pip install lxml
from bs4 import BeautifulSoup
from bs4.element import Comment

str1 = '''
<title id='title'>尚学堂</title>
<div class='info' float='left'>Welcome to SXT</div>
<div class='info' float='right'>
    <span>Good Good Study</span>
    <a href='www.bjsxt.cn'></a>
    <strong><!--没用--></strong>
</div>
'''
soup = BeautifulSoup(str1, 'lxml')
print('-' * 30, '获取标签', '-' * 30)
print(soup.title)
print(soup.span)
print(soup.div)

print('-' * 30, '获取属性', '-' * 30)
print(soup.div.attrs)
print(soup.div.get('class'))
print(soup.a['href'])

print('-' * 30, '获取内容', '-' * 30)
print(type(soup.title.string))
print(soup.title.text)

print(type(soup.strong.string))
print(soup.strong.text)

if type(soup.strong.string) == Comment:
    print('有注释!')
    print(soup.strong.prettify())

print('-' * 30, 'find_all()', '-' * 30)
print(soup.find_all('div'))
print(soup.find_all(id='title'))
print(soup.find_all(class_='info'))
print(soup.find_all(attrs={'float': 'right'}))

print('-' * 30, 'select()', '-' * 30)
print(soup.select('a'))
print(soup.select('#title'))
print(soup.select('.info'))
print(soup.select('div span'))
print(soup.select('div > span'))

3.3. Xpath

  • 可以在Google Chrome上安装 Xpath Helper插件。

1. 介绍、安装

  1. 之前 BeautifulSoup 的用法,这个已经是非常强大的库了,不过还有一些比较流行的解析库,例如 lxml,使用的是 Xpath 语法,同样是效率比较高的解析方法。如果大家对 BeautifulSoup 使用不太习惯的话,可以尝试下 Xpath

    官网 http://lxml.de/index.html

    w3c http://www.w3school.com.cn/xpath/index.asp

  2. 安装:

    pip install lxml
    

2. Xpath 语法

  1. XPath 是一门在 XML 文档中查找信息的语言。XPath 可用来在 XML 文档中对元素和属性进行遍历。XPath 是 W3C XSLT 标准的主要元素,并且 XQuery 和 XPointer 都构建于 XPath 表达之上

  2. 节点关系

    • 父(Parent)
    • 子(Children)
    • 同胞(Sibling)
    • 先辈(Ancestor)
    • 后代(Descendant)

3. 获取节点:

  1. 常用的路径表达式
表达式 描述
nodename 选取此节点的所有子节点
/ 从根节点选取
// 从匹配选择的当前节点选择文档中的节点,而不考虑它们的位置
. 选取当前节点
选取当前节点的父节点
@ 选取属性
  • 示例:
    第十二阶段 -- 爬虫02:【request;数据提取(正则,Beautiful Soup,xpath)】
    第十二阶段 -- 爬虫02:【request;数据提取(正则,Beautiful Soup,xpath)】
  1. 通配符:XPath 通配符可用来选取未知的 XML 元素。
通配符 描述 举例 结果
* 匹配任何元素节点 xpath(‘div/*’) 获取div下的所有子节点
@* 匹配任何属性节点 xpath(‘div[@*]’) 选取所有带属性的div节点
node() 匹配任何类型的节点
  1. 选取若干路径:通过在路径表达式中使用“|”运算符,您可以选取若干个路径
表达式 结果
xpath(’//div|//table’) 获取所有的div与table节点
  1. 谓语:谓语被嵌在方括号内,用来查找某个特定的节点或包含某个制定的值的节点
表达式 结果
xpath(’/body/div[1]’) 选取body下的第一个div节点
xpath(’/body/div[last()]’) 选取body下最后一个div节点
xpath(’/body/div[last()-1]’) 选取body下倒数第二个节点
xpath(’/body/div[positon() < 3 ]’) 选取body下前丙个div节点
xpath(’/body/div[@class]’) 选取body下带有class属性的div节点
xpath(’/body/div[@class=“main”]’) 选取body下class属性为main的div节点
xpath(’/body/div[price>35.00]’) 选取body下price元素大于35的div节点
  1. Xpath 运算符
运算符 描述 实例 返回值
计算两个节点集 //book
+ 加法 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

4. 使用

1. 例子:

from lxml import etree

text = '''
<div>
    <ul>
         <li class="item-0"><a href="link1.html">first item</a></li>
         <li class="item-1"><a href="link2.html">second item</a></li>
         <li class="item-inactive"><a href="link3.html">third item</a></li>
         <li class="item-1"><a href="link4.html">fourth item</a></li>
         <li class="item-0"><a href="link5.html">fifth item</a>
     </ul>
 </div>
'''

html = etree.HTML(text)
result = etree.tostring(html)
print(result)
  1. 首先我们使用 lxml 的 etree 库,然后利用 etree.HTML 初始化,然后我们将其打印出来。
    其中,这里体现了 lxml 的一个非常实用的功能就是自动修正 html 代码,大家应该注意到了,最后一个 li 标签,其实我把尾标签删掉了,是不闭合的。不过,lxml 因为继承了 libxml2 的特性,具有自动修正 HTML 代码的功能。
  • 所以输出结果是这样的:
<html><body>
<div>
    <ul>
         <li class="item-0"><a href="link1.html">first item</a></li>
         <li class="item-1"><a href="link2.html">second item</a></li>
         <li class="item-inactive"><a href="link3.html">third item</a></li>
         <li class="item-1"><a href="link4.html">fourth item</a></li>
         <li class="item-0"><a href="link5.html">fifth item</a></li>
</ul>
</div>

</body></html>

不仅补全了 li 标签,还添加了 body,html 标签。

  1. 文件读取:
    除了直接读取字符串,还支持从文件读取内容。比如我们新建一个文件叫做 hello.html,
    内容为:
<div>
    <ul>
         <li class="item-0"><a href="link1.html">first item</a></li>
         <li class="item-1"><a href="link2.html">second item</a></li>
         <li class="item-inactive"><a href="link3.html"><span class="bold">third item</span></a></li>
         <li class="item-1"><a href="link4.html">fourth item</a></li>
         <li class="item-0"><a href="link5.html">fifth item</a></li>
     </ul>
 </div>
  1. 利用 parse 方法来读取文件:
from lxml import etree
html = etree.parse('hello.html')
result = etree.tostring(html, pretty_print=True) # 解析成字节
print(result)

同样可以得到相同的结果。

2. XPath 具体使用:

  1. 获取所有的 <li> 标签
from lxml import etree
html = etree.parse('hello.html')
print (type(html))
result = html.xpath('//li')
print (result)
print (len(result))
print (type(result))
print (type(result[0]))
  • 运行结果:
<type 'lxml.etree._ElementTree'>
[<Element li at 0x1014e0e18>, <Element li at 0x1014e0ef0>, <Element li at 0x1014e0f38>, <Element li at 0x1014e0f80>, <Element li at 0x1014e0fc8>]

<type 'list'>
<type 'lxml.etree._Element'>

可见,etree.parse 的类型是 ElementTree,通过调用 xpath 以后,得到了一个列表,包含了 5 个 <li> 元素,每个元素都是 Element 类型。

  1. 获取<li>标签的所有 class
result = html.xpath('//li/@class')
print (result)
  • 运行结果:
['item-0', 'item-1', 'item-inactive', 'item-1', 'item-0']
  1. 获取 <li> 标签下 href 为 link1.html 的 <a> 标签
# 写Xpath的时,外边单引号,里面就需要全部双引号;反之亦然
result = html.xpath('//li/a[@href="link1.html"]')
print (result)
  • 运行结果:
[<Element a at 0x10ffaae18>]
  1. 获取<li>标签下的所有 <span> 标签

    注意: 这么写是不对的

result = html.xpath('//li/span')

#因为 / 是用来获取子元素的,而 <span> 并不是 <li> 的子元素,所以,要用双斜杠
result = html.xpath('//li//span')
print(result)
  • 运行结果:
[<Element span at 0x10d698e18>]
  1. 获取 <li> 标签下的所有 class,不包括<li>
result = html.xpath('//li/a//@class')
print (result)
# 运行结果
# ['blod']
  1. 获取最后一个 <li><a> 的 href
result = html.xpath('//li[last()]/a/@href')
print (result)
  • 运行结果:
['link5.html']
  1. 获取倒数第二个元素的内容
result = html.xpath('//li[last()-1]/a')
print (result[0].text)
  • 运行结果:
fourth item
  1. 获取 class 为 bold 的标签名
result = html.xpath('//*[@class="bold"]')
print (result[0].tag)
  • 运行结果:
span

选择XML文件中节点

  • element(元素节点)
  • attribute(属性节点)
  • text (文本节点)
  • concat(元素节点,元素节点)
  • comment (注释节点)
  • root (根节点)

5. 代码示例

  • 在纵横网,爬取前三页的数据,只要书名
from lxml import etree
import requests
from fake_useragent import UserAgent

url = 'http://book.zongheng.com/store/c1/c0/b0/u0/p1/v9/s1/t0/u0/i1/ALL.html'
headers = {'User-Agent': UserAgent().chrome}
resp = requests.get(url, headers=headers)
html = resp.text

# 构造解析对象 etree
e = etree.HTML(html)

# 书名
names = e.xpath('//div[@class="bookname"]/a/text()')
# 作者
authors = e.xpath('//div[@class="bookilnk"]/a[1]/text()')
# 方式01:如果没有作者,就不对应了
for i in range(len(names)):
    print('{}:{}'.format(names[i], authors[i]))
# 方式02:如果迭代器数量不一样,选择走短的那个
for n, a in zip(names, authors):
   print('{}:{}'.format(n,a))

上一篇:Python里的17个超赞操作,让你的技法更beautiful


下一篇:D - Beautiful Graph CodeForces - 1093D (二分图染色+方案数)