快速入门 Python 爬虫常用解析库(xpathbs4)

Posted Amo Xiang

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了快速入门 Python 爬虫常用解析库(xpathbs4)相关的知识,希望对你有一定的参考价值。

第一章 XPath 解析

在 Python 中可以支持 XPath 提取数据的解析模块有很多,本文主要介绍 lxml 模块,该模块可以解析 html 与 XML,并且支持 XPath 解析方式。由于 lxml 模块 为第三方模块,需要通过 pip install lxml 命令安装该模块。lxml 模块 的底层是通过C语言编写的,所以在解析效率方面是非常优秀的。xpath 语法参考学习网站:https://www.w3school.com.cn/xpath/xpath_nodes.asp。

【示例1】使用 parse() 方法解析本地的 HTML 文件。(一般不怎么常用)

笔者在自己本地新建了一个 demo.html,内容如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>lxml模块读取本地html文件测试</title>
</head>
<body>
hello, lxml!
</body>
</html>

python 代码如下:

from lxml import etree  # 导入etree子模块

parser = etree.HTMLParser()  # 创建HTMLParser对象
html = etree.parse('demo.html', parser=parser)  # 解析demo.html文件
print(type(html))  # <class 'lxml.etree._ElementTree'> 是一个对象
html_txt = etree.tostring(html, encoding="utf-8")  # 转换字符串类型,并进行编码
print(html_txt.decode('utf-8'))  # 打印解码后的HTML代码

【示例2】解析字符串类型的 HTML 代码。etree 子模块还提供了一个 HTML() 方法,该方法可以实现解析字符串类型的 HTML 代码。示例代码如下:

from lxml import etree  # 导入etree子模块

# 定义html字符串
html_str = '''<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>解析字符串类型的 HTML</title>
</head>
<body>
hello, lxml!
</body>
</html>'''
html = etree.HTML(html_str)  # 解析html字符串
html_txt = etree.tostring(html, encoding="utf-8")  # 转换字符串类型,并进行编码
print(html_txt.decode('utf-8'))  # 打印解码后的HTML代码

【示例3】解析服务器返回的 HTML 代码。在实际开发中,HTML() 方法的使用率是非常高的,因为发送网络请求后,多数情况下都会将返回的响应结果转换为字符串类型,如果返回的结果是 HTML 代码,则需要使用 HTML() 方法来进行解析。示例代码如下:

from lxml import etree  # 导入etree子模块
import requests  # 导入requests模块
from requests.auth import HTTPBasicAuth  # 导入HTTPBasicAuth类

# 定义请求地址
url = 'http://sck.rjkflm.com:666/spider/auth/'
# 实现验证请求功能
ah = HTTPBasicAuth('admin', 'admin')  # 创建HTTPBasicAuth对象,参数为用户名与密码
response = requests.get(url=url, auth=ah)  # 发送网络请求
if response.status_code == 200:  # 如果请求成功
    html = etree.HTML(response.text)  # 解析html字符串
    html_txt = etree.tostring(html, encoding="utf-8")  # 转换字符串类型,并进行编码
    print(html_txt.decode('utf-8'))  # 打印解码后的HTML代码
"""
分析: 运行结果中会出现&#13; 表示 Unicode 编码的回车字符。
"""

【示例4】使用 // 获取HTML代码的所有节点 ,示例如下:

html_str = '''
<div class="level_one on">
<ul>
<li> <a href="/index/index/view/id/1.html" title="什么是Java" class="on">什么是Java</a> </li>
<li> <a href="javascript:" οnclick="login(0)" title="Java的版本">Java的版本</a> </li>
<li> <a href="javascript:" οnclick="login(0)" title="Java API文档">Java API文档</a> </li>
<li> <a href="javascript:" οnclick="login(0)" title="JDK的下载">JDK的下载</a> </li>
<li> <a href="javascript:" οnclick="login(0)" title="JDK的安装">JDK的安装</a> </li>
<li> <a href="javascript:" οnclick="login(0)" title="配置JDK">配置JDK</a> </li>
</ul>
</div>
'''
html = etree.HTML(html_str)  # 解析html字符串
node_all = html.xpath('//*')  # 获取所有节点
print('数据类型:', type(node_all))  # 打印数据类型
print('数据长度:', len(node_all))  # 打印数据长度
print('数据内容:', node_all)  # 打印数据内容
# 通过推导式打印所有节点名称,通过节点对象.tag获取节点名称
print('节点名称:', [i.tag for i in node_all])
######################################################
html = etree.HTML(html_str)  # 解析html字符串
li_all = html.xpath('//li')  # 获取所有li节点
print('所有li节点', li_all)  # 打印所有li节点
print('获取指定li节点:', li_all[1])  # 打印指定li节点
li_txt = etree.tostring(li_all[1], encoding="utf-8")  # 转换字符串类型,并进行编码
# 打印指定节点的HTML代码
print('获取指定节点HTML代码:', li_txt.decode('utf-8'))

程序运行结果如下:

数据类型: <class 'list'>
数据长度: 16
数据内容: [<Element html at 0x20aec9e8088>, <Element body at 0x20aecabee48>, <Element div at 0x20aecabef08>, <Element ul at 0x20aecabef48>, <Element li at 0x20aecabef88>, <Element a at 0x20aecac5048>, <Element li at 0x20aecac5088>, <Element a at 0x20aecac50c8>, <Element li at 0x20aecac5108>, <Element a at 0x20aecabefc8>, <Element li at 0x20aecac5148>, <Element a at 0x20aecac5188>, <Element li at 0x20aecac51c8>, <Element a at 0x20aecac5208>, <Element li at 0x20aecac5248>, <Element a at 0x20aecac5288>]
节点名称: ['html', 'body', 'div', 'ul', 'li', 'a', 'li', 'a', 'li', 'a', 'li', 'a', 'li', 'a', 'li', 'a']

所有li节点 [<Element li at 0x20aec9fad48>, <Element li at 0x20aecac5c88>, <Element li at 0x20aecac5d08>, <Element li at 0x20aecac5dc8>, <Element li at 0x20aecac5e08>, <Element li at 0x20aecac5e88>]
获取指定li节点: <Element li at 0x20aecac5c88>
获取指定节点HTML代码: <li> <a href="javascript:" οnclick="login(0)" title="Java的版本">Java的版本</a> </li>

【示例5】使用 / 获取一个节点中的直接子节点,示例如下:

# 定义html字符串
html_str = '''
<div class="level_one on">
<ul>
<li>
    <a href="/index/index/view/id/1.html" title="什么是Java" class="on">什么是Java</a>
    <a>Java</a> 
</li>
<li> <a href="javascript:" οnclick="login(0)" title="Java的版本">Java的版本</a> </li>
<li> <a href="javascript:" οnclick="login(0)" title="Java API文档">Java API文档</a> </li>
</ul>
</div>
'''
html = etree.HTML(html_str)  # 解析html字符串
a_all = html.xpath('//li/a')  # 获取li节点中所有子节点a
print('所有子节点a', a_all)  # 打印所有a节点
print('获取指定a节点:', a_all[1])  # 打印指定a节点
a_txt = etree.tostring(a_all[1], encoding="utf-8")  # 转换字符串类型,并进行编码
# 打印指定节点的HTML代码
print('获取指定节点HTML代码:', a_txt.decode('utf-8'))

程序运行结果如下:

所有子节点a [<Element a at 0x20aecac7a48>, <Element a at 0x20aecac7a88>, <Element a at 0x20aecac7ac8>, <Element a at 0x20aecac7b08>]
获取指定a节点: <Element a at 0x20aecac7a88>
获取指定节点HTML代码: <a>Java</a> 

【示例6】使用 // 实现获取子孙节点,示例如下:

# 定义html字符串
html_str = '''
<div class="level_one on">
<ul>
<li>
    <a href="/index/index/view/id/1.html" title="什么是Java" class="on">什么是Java</a>
    <a>Java</a> 
</li>
<li> <a href="javascript:" οnclick="login(0)" title="Java的版本">Java的版本</a> </li>
<li> 
    <a href="javascript:" οnclick="login(0)" title="Java API文档">
        <a>a节点中的a节点</a>
    </a>
</li>
</ul>
</div>
'''
html = etree.HTML(html_str)    # 解析html字符串
a_all = html.xpath('//ul//a')   # 获取ul节点中所有子孙节点a
print('所有子节点a',a_all)    # 打印所有a节点
print('获取指定a节点:',a_all[4])  # 打印指定a节点
a_txt = etree.tostring(a_all[4],encoding = "utf-8")   # 转换字符串类型,并进行编码
# 打印指定节点的HTML代码
print('获取指定节点HTML代码:',a_txt.decode('utf-8'))

程序运行结果如下:

所有子节点a [<Element a at 0x20aec9e8708>, <Element a at 0x20aecac8388>, <Element a at 0x20aecac83c8>, <Element a at 0x20aecac8408>, <Element a at 0x20aecac8448>]
获取指定a节点: <Element a at 0x20aecac8448>
获取指定节点HTML代码: <a>a节点中的a节点</a>

【示例7】使用 … 获取一个节点的父节点,示例如下:

# 定义html字符串
html_str = '''
<div class="level_one on">
<ul>
<li><a href="/index/index/view/id/1.html" title="什么是Java" class="on">什么是Java</a></li>
<li> <a href="javascript:" οnclick="login(0)" title="Java的版本">Java的版本</a> </li>
</ul>
</div>
'''
html = etree.HTML(html_str)    # 解析html字符串
a_all_parent = html.xpath('//a/..')   # 获取所有a节点的父节点
print('所有a的父节点',a_all_parent)    # 打印所有a的父节点
print('获取指定a的父节点:',a_all_parent[0])  # 打印指定a的父节点
a_txt = etree.tostring(a_all_parent[0],encoding = "utf-8")   # 转换字符串类型,并进行编码
# 打印指定节点的HTML代码
print('获取指定节点HTML代码:\\n',a_txt.decode('utf-8'))
# 除了使用“..”获取一个节点的父节点以外,还可以使用“/parent::*”的方式来获取。
# 例子:获取li的父节点
ul = html.xpath("//li/parent::*")
print(ul)

程序运行结果如下:

所有a的父节点 [<Element li at 0x20aee0a8e48>, <Element li at 0x20aee0a8088>]
获取指定a的父节点: <Element li at 0x20aee0a8e48>
获取指定节点HTML代码:
 <li><a href="/index/index/view/id/1.html" title="什么是Java" class="on">什么是Java</a></li>

[<Element ul at 0x20aee0a8a08>]

【示例8】使用 text() 方法获取HTML代码中的文本,示例如下:

# 定义html字符串
html_str = '''
<div class="level_one on">
<ul>
<li><a href="/index/index/view/id/1.html" title="什么是Java" class="on">什么是Java</a></li>
<li> <a href="javascript:" οnclick="login(0)" title="Java的版本">Java的版本</a> </li>
</ul>
</div>
'''
html = etree.HTML(html_str)    # 解析html字符串
a_text = html.xpath('//a/text()')   # 获取所有a节点中的文本信息
print('所有a节点中文本信息:',a_text)

程序运行结果如下:

所有a节点中文本信息: ['什么是Java', 'Java的版本']

【示例9】使用[@…] 实现节点属性的匹配,示例如下:

# 定义html字符串
html_str = '''
<div class="video_scroll">
    <div class="level">什么是Java</div>
    <div class="level">Java的版本</div>
</div>
'''
html = etree.HTML(html_str)    # 解析html字符串
# 获取所有class="level"的div节点中的文本信息 @后面加对应属性名都可
div_one = html.xpath('//div[@class="level"]/text()')
print(div_one)     # 打印class="level"的div中文本

程序运行结果如下:

['什么是Java', 'Java的版本']

【示例10】属性多值匹配进行节点内容的筛选,示例如下:

# 定义html字符串
html_str = '''
<div class="video_scroll">
    <div class="level one">什么是Java</div>
    <div class="level">Java的版本</div>
</div>
'''
html = etree.HTML(html_str)    # 解析html字符串
# 获取所有class="level one"的div节点中的文本信息
div_one = html.xpath('//div[@class="level one"]/text()')
print(div_one)     # 打印class="level one"的div中文本


html = etree.HTML(html_str)    # 解析html字符串
# 获取所有class属性值中包含level的div节点中的文本信息
div_all = html.xpath('//div[contains(@class,"level")]/text()')
print(div_all)     # 打印所有符合条件的文本信息

程序运行结果如下:

['什么是Java']
['什么是Java', 'Java的版本']

【示例11】一个节点中多个属性的匹配,示例如下:

# 定义html字符串
html_str = '''
<div class="video_scroll">
    <div class="level" id="one">什么是Java</div>
    <div class="level">Java的版本</div>
</div>
'''
html = etree.HTML(html_str)    # 解析html字符串
# 获取所有符合class="level与id="one"的div节点中的文本信息
div_all = html.xpath('//div[@class="level" and @id="one"]/text()')
print(div_all)     # 打印所有符合条件的文本信息

程序运行结果如下:

['什么是Java']

【示例12】使用 @获取属性所对应的值,示例如下:

# 定义html字符串
html_str = '''
<div class="video_scroll">
    <li class="level" id="one">什么是Java</li>
</div>
'''
html = etree.HTML(html_str)    # 解析html字符串
# 获取li节点中的class属性值
li_class = html.xpath('//div/li/@class')
# 获取li节点中的id属性值
li_id = html.xpath('//div/li/@id')
print('class属性值:',li_class)
print('id属性值:',li_id)

程序运行结果如下:

class属性值: ['level']
id属性值: ['one']

【示例13】使用索引按序获取属性对应的值,示例如下:

# 定义html字符串
html_str = '''
<div class="video_scroll">
    <li> <a href="javascript:" οnclick="login(0)" title="Java API文档">Java API文档</a> </li>
    <li> <a href="javascript:" οnclick="login(0)" title="JDK的下载">JDK的下载</a> </li>
    <li> <a href="javascript:" οnclick="login(0)" title="JDK的安装">JDK的安装</a> </li>
    <li> <a href="javascript:" οnclick="login(0)" title="配置JDK">配置JDK</a> </li>
</div>
'''
html = etree.HTML(html_str)    # 解析html字符串
# 获取所有li/a节点中title属性值
li_all = html.xpath('//div/li/a/@title')
print('所有属性值:',li_all)  
# 获取第1个li/a节点中title属性值
li_first = html.xpath('//div/li[1]/a/@title')
print('第一个属性值:',li_first)  # 最后的结果哪怕只有一个,也是以列表的形式进行返回
# 获取第4个li/a节点中title属性值
li_four = html.xpath('//div/li[4]/a/@title')
print('第四个属性值:',li_four)



html = etree.HTML(html_str)    # 解析html字符串
# 获取最后一个li/a节点中title属性值
li_last = html.xpath('//div/li[last()]/a/@title')
print('最后一个属性值:',li_last)
# 获取第1个li/a节点中title属性值
li = html.xpath('//div/li[position()=1]/a/@title')
print('第一个位置的属性值:',li)
# 获取倒数第二个li/a节点中title属性值
li = html.xpath('//div/li[last()-1]/a/@title')
print('倒数第二个位置的属性值:',li)
# 获取位置大于1的li/a节点中title属性值
li = html.xpath('//div/li[position()>1]/a/@title')
print('位置大于1的属性值:',li)

程序运行结果如下:

所有属性值: ['Java API文档', 'JDK的下载', 'JDK的安装', '配置JDK']
第一个属性值: ['Java API文档']
第四个属性值: ['配置JDK']
最后一个属性值: ['配置JDK']
第一个位置的属性值: ['Java API文档']
倒数第二个位置的属性值: ['JDK的安装']
位置大于1的属性值: ['JDK的下载', 'JDK的安装', '配置JDK']

【示例14】使用节点轴获取节点内容,示例如下:

# 定义html字符串
html_str = '''
<div class="video_scroll">
    <li><a href="javascript:" οnclick="login(0)" title="Java API文档">Java API文档</a></li> 
    <li><a href="javascript:" οnclick="login(0)" title="JDK的下载">JDK的下载</a></li> 
    <li> <a href="javascript:" οnclick="login(0)" title="JDK的安装">JDK的安装</a> </li>
</div>
'''

html = etree.HTML(html_str)    # 解析html字符串
# 获取li[2]所有祖先节点
ancestors = html.xpath('//li[2]/ancestor::*')
print('li[2]所有祖先节点名称:',[i.tag for i in ancestors])
# 获取li[2]祖先节点位置为body
body = html.xpath('//li[2]/ancestor::body')
print('li[2]指定祖先节点名称:',[i.tag for i in body])
# 获取li[2]属性为class="video_scroll"的祖先节点
class_div = html.xpath('//li[2]/ancestor::*[@class="video_scroll"]')
print('li[2]class="video_scroll"的祖先节点名称:',[i.tag for i in class_div])
# 获取li[2]/a所有属性值
attributes = html.xpath('//li[2]/a/attribute::*')
print('li[2]/a的所有属性值:',attributes)
# 获取div所有子节点
div_child = html.xpath('//div/child::*')
print('div的所有子节点名称:',[i.tag for i in div_child])
# 获取body所有子孙节点
body_descendant = html.xpath('//body/descendant::*')
print('body的所有子孙节点名称:',[i.tag for i in body_descendant])
# 获取li[1]节点后的所有节点
li_following = html.xpath('//li[1]/following::*')
print('li[1]之后的所有节点名称:',[i.tag for i in li_following])
# 获取li[1]节点后的所有同级节点
li_sibling = html.xpath('//li[1]/following-sibling::*')
print('li[1]之后的所有同级节点名称:',[i.tag for i in li_sibling])
# 获取li[3]节点前的所有节点
li_preceding = html.xpath('//li[3]/preceding::*')
print('li[3]之前的所有节点名称:',[i.tag for i in li_preceding])

程序运行结果如下:

li[2]所有祖先节点名称: ['html', 'body', 'div']
li[2]指定祖先节点名称: ['body']
li[2]class="video_scroll"的祖先节点名称: ['div']
li[2]/a的所有属性值: ['javascript:', 'login(0)', 'JDK的下载']
div的所有子节点名称: ['li', 'li', 'li']
body的所有子孙节点名称: ['div', 'li', 'a', 'li', 'a', 'li', 'a']
li[1]之后的所有节点名称: ['li', 'a', 'li', 'a']
li[1]之后的所有同级节点名称: ['li', 'li']
li[3]之前的所有节点名称: ['li', 'a', 'li', 'a']

总结:

第二章 Beautiful Soup 模块

模块介绍请看:https://blog.csdn.net/xw1680/article/details/105845918

【示例1】使用 Beautiful Soup解析HTML代码。示例如下:

from bs4 import BeautifulSoup  # 导入BeautifulSoup库

# 创建模拟HTML代码的字符串
html_doc = """
<html>
<head>
<title>第一个 HTML 页面</title>
</head>
<body>
<p>body 元素的内容会显示在浏览器中。</p>
<p>title 元素的内容会显示在浏览器的标题栏中。</p>
</body>
</html>
"""
# 创建一个BeautifulSoup对象,获取页面正文
soup = BeautifulSoup(html_doc, features="lxml")
print(soup)                  # 打印解析的HTML代码
print(type(soup))            # 打印数据类型
# 如果将html格式字符串的代码保存在 demo.html 文件中,可以通过打开HTML文件的方式进行代码的解析,
# 并且可以通过prettify()方法进行代码的格式化处理,代码如下:
# soup = BeautifulSoup(open("demo.html", encoding="utf8"), features="lxml")
# print(soup.prettify())

程序运行结果如下:

<html>
<head>
<title>第一个 HTML 页面</title>
</head>
<body>
<p>body 元素的内容会显示在浏览器中。</p>
<p>title 元素的内容会显示在浏览器的标题栏中。</p>
</body>
</html>

<class 'bs4.BeautifulSoup'>
<!DOCTYPE html>
<html lang="en">
 <head>
  <meta charset="utf-8"/>
  <title>
   lxml模块读取本地html文件测试
  </title>
 </head>
 <body>
  hello, lxml!
 </body>
</html>

【示例2】使用 Beautiful Soup 模块获取节点对应的代码。示例如下:

# 创建模拟HTML代码的字符串
html_doc = """
<html>
<head>
<title>第一个 HTML 页面</title>
</head>
<body>
<p>body 元素的内容会显示在浏览器中。</p>
<p>title 元素的内容会显示在浏览器的标题栏中。</p>
</body>
</html>
"""

# 创建一个BeautifulSoup对象,获取页面正文
soup = BeautifulSoup(html_doc, features="lxml")
print('head节点内容为:\\n',soup.head)               # 打印head节点
print('body节点内容为:\\n',soup.body)               # 打印body节点
print('title节点内容为:\\n',soup.title)              # 打印title节点
print('p节点内容为:\\n',soup.p)                  # 打印p节点
"""
在打印p节点对应的代码时,可以发现只打印了第一个p节点内容,这说明当有多个节点时,该选择方式只会获取第一个节点中的内容
其他后面的节点将被忽略。
"""
#获取节点名称
print(soup.head. name)
print( soup.body . name)
print(soup.title.name)
print(soup.p.name)

程序运行结果如下:

head节点内容为:
 <head>
<title>第一个 HTML 页面</title>
</head>
body节点内容为:
 <body>
<p>body 元素的内容会显示在浏览器中。</p>
<p>title 元素的内容会显示在浏览器的标题栏中。</p>
</body>
title节点内容为:
 <title>第一个 HTML 页面</title>
p节点内容为:
 <p>body 元素的内容会显示在浏览器中。</p>
head
body
title
p

【示例3】获取节点属性。示例如下:

# 创建模拟HTML代码的字符串
html_doc = """
<html>
<head>
    <title>横排响应式登录</title>
    <meta http-equiv="Content-Type" content="text/html" charset="utf-8"/>
    <meta name="viewport" content="width=device-width"/>
    <link href="font/css/bootstrap.min.css" type="text/css" rel="stylesheet">
    <link href="css/style.css" type="text/css" rel="stylesheet">
</head>
<body>
<h3>登录</h3>
<div class="glyphicon glyphicon-envelope"><input type="text" placeholder="请输入邮箱"></div>
<div class="glyphicon glyphicon-lock"><input type="password" placeholder="请输入密码"></div>
</body>
</html>
"""
# 创建一个BeautifulSoup对象,获取页面正文
soup = BeautifulSoup(html_doc, features="lxml")
print('meta节点中属性如下:\\n',soup.meta.attrs)
print('link节点中属性如下:\\n',soup.link.attrs)
print('div节点中属性如下:\\n',soup.div.attrs)
print('-------------------------------分割线-------------------------------')
# 在attrs后面添加括号并在括号内添加属性名称即可获取指定属性对应的值。
print ( 'meta节点中http-equiv属性对应的值为: ',soup.meta.attrs[ 'http-equiv'])
print( 'link节点中href属性对应的值为: ',soup.link.attrs [ 'href']Python3爬虫解析库之XPath详解

快速入门Python爬虫|requests请求库|pyquery定位库

python爬虫入门

Python 爬虫 解析库的使用 --- XPath

python爬虫基础04-网页解析库xpath

Python3编写网络爬虫05-基本解析库XPath的使用