爬虫004----网页解析库

发布于:2025-06-25 ⋅ 阅读:(19) ⋅ 点赞:(0)

    网页解析,反正。。。。不是很多,就放一起了,常用的也就仨,漂亮的汤🥣(Beautiful
Soup)、lxml、re正则匹配(反正都是做解析用的,就把re放进来了)

     PS: 建议稍微学习学习前端知识,HTML、CSS即可,对做爬虫网页解析大有帮助

1. lxml(xpath)

1.1 介绍

高效且灵活的 Python 库,用于处理和解析 HTML 和 XML 文档。

  1. 高性能:
    lxml 提供了非常快的解析速度,尤其适合处理大型 XML/HTML 文件。
  2. 完整的 DOM 和 XPath 支持:
    支持完整的文档对象模型 (DOM) API,可以方便地构建、修改和查询文档。
    支持 XPath 查询,可以通过简单的表达式来查找和选择指定的节点。
  3. HTML 解析:
    lxml 能够智能处理和解析不太符合标准的 HTML 文档,这使它在 Web 抓取时非常有用。
  4. 简洁的 API:
    提供易于使用的 API,适合新手快速上手,同时也能满足高级用户的需求。
  5. 支持 XSLT 和 XML Schema:
    lxml 能够进行转换和验证操作,使得处理 XML 文档更为灵活。

ChatGPT上CV来的,CV大法好,叭叭叭说了这么多优点,当然我们主要用来抓取网页和解析用

1.2 官方文档和导入

https://www.w3cschool.cn/lxml/.html
这里给的是w3c上的中文文档,我觉得排版挺清晰的,方便观看,就是感觉网页有点慢,可能是我电脑卡

# 模块安装
pip install lxml

# 导包
import lxml

# 爬虫常用的是etree模块进行HTML解析
from lxml import etree

1.3 Xpath表达式

这也是lxml比较好的一点,从网页copy下来的xpath可以直接使用,类似于css选择器语法,但bs4就有自己的表达式,需要去手动改写

表达式 作用 例子🌰
节点名
选取此节点的所有子节点
div
/
从当前节点选取直接子节点(只能选择子节点,孙节点选取不了)
/div
//
从当前节点选取子孙节点
//div
.
选取当前节点
..
选取当前节点的父节点
@
选取属性
//@class
*
通配符,选择所有元素节点与元素名
@*
选取所有属性
[@attrib]
选取具有给定属性的元素
//div[@id]
[@sttrib='value']
选取给定属性具有给定值的所有元素
//div[@id="maincontent"]
[tag]
选取多有具有指定元素的直接子节点
[div]
[tag='text']
选取所有具有指定元素并且文本内容是text节点
[div="text"]
模糊查询
例:查询id属性中带有he的节点
//div[contains(@id, "he")]
模糊查询
例:查询id属性中以he开头的节点
//div[starts-with(@id, "he")]
内容查询
例:查询div/h1中文本内容
//div/h1/text()
逻辑运算
例:查询id为head并且class为s_down的元素
//div[@id="head" and @class="s_down"]
管道运算
例:选取title属性后再选取带有price属性的元素
//title | //price

1.4 lxml解析流程

lxml解析本地文件和服而且响应文件,并且获取想要的数据

# 导入etree模块
from lxml import etree

# 解析本地文件
html_tree = etree.parse("xx.html")

# 解析服务器响应文件
html_tree = etree.HTML(response.read().decode('utf-8'))

# 获取想要数据
html_etree.xpath("xpath路径")

来一个大的实战性例子🌰
1. 爬取站长素材,并且根据指定页数批量获取多页高清图片,保存到本地
此案例使用了urllib库,正好复习一下之前的urllib,可能有点写复杂了,这种简单实例用函数其实也可以实现

import os.path
import urllib.request
from lxml import etree

def get_page():
	"""手动输入需要爬取网页图片的开始结束页码函数"""
    while True:
        try:
            start_page = int(input("请输入起始页数:"))
            end_page = int(input("请输入结束页码:"))
            if start_page <= end_page:
                return start_page, end_page
            else:
                print('输入页码有误!!无结果')
        except ValueError:
            print("输入无效!!")

class GetWomenPicture:
	"""图片获取以及下载类"""
    url = 'https://sc.chinaz.com/tupian/ribenmeinv.html'

    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36',
        'Cookie': "cz_statistics_visitor=f4a94798-a16d-0814-b3de-45735ada46ab; Hm_lvt_9812c79f0a6ed79d80874364fad1b8f2=1747732748; Hm_lvt_398913ed58c9e7dfe9695953fb7b6799=1750418659,1750419273,1750419343,1750419624; HMACCOUNT=14F454179BE4E8D6; _clck=1u417iu%7C2%7Cfwx%7C0%7C1940; Hm_lvt_ca96c3507ee04e182fb6d097cb2a1a4c=1750432138; HMACCOUNT=14F454179BE4E8D6; Hm_lpvt_ca96c3507ee04e182fb6d097cb2a1a4c=1750432159; _clsk=1jfr2nl%7C1750432159578%7C4%7C1%7Cy.clarity.ms%2Fcollect; ucvalidate=b6ae96da-e023-f4ca-61ee-e0858b097747; CzScCookie=4e6f035d-ad7d-a899-ab00-d713fd807c89; Hm_lpvt_398913ed58c9e7dfe9695953fb7b6799=1750432307"
    }

    def __init__(self):
        self.html = None

        if not os.path.exists('./page'):
            os.makedirs('./page')

    def get_first_page(self):
        requests = urllib.request.Request(url=self.url, headers=self.headers)
        response = urllib.request.urlopen(requests)
        self.html = response.read().decode('utf-8')

    def get_all_women_picture(self):
        self.url = 'https://sc.chinaz.com/tupian/ribenmeinv_{0}.html'.format(str(i))
        requests = urllib.request.Request(url=self.url, headers=self.headers)
        response = urllib.request.urlopen(requests)
        self.html = response.read().decode('utf-8')

    def download_women_picture(self):
        tree = etree.HTML(self.html)
        name_list = tree.xpath('//div[@class="item"]/img/@alt')
        src_list = tree.xpath('//div[@class="item"]/img/@data-original')
        for i in range(len(name_list)):
            name = name_list[i]
            src = src_list[i]
            name = "".join(c for c in name if c.isalnum() or c in (' ', '-', '_')).strip()
            urllib.request.urlretrieve(url=src, filename="./page/{0}.jpg".format(name))

class GetPics:
	"""实现批量下载类"""	
    def __init__(self, num):
        self.num = num
    def download_pics(self):
        print("正在爬取第{0}页图片...".format(self.num))
        if self.num == 1:
            get_women_pic.get_first_page()
            get_women_pic.download_women_picture()
        else:
            get_women_pic.get_all_women_picture()
            get_women_pic.download_women_picture()


if __name__ == '__main__':
    start_page, end_page = get_page()
    get_women_pic = GetWomenPicture()
    for i in range(start_page, end_page + 1):
        get_pic = GetPics(i)
        get_pic.download_pics()

2. 漂亮的汤

2.1 介绍

Beautiful Soup,简称bs4,是一个HTML解析器,主要用于解析和提取数据用的
优点: 接口人性化,使用方便
缺点: 效率低,比lxml低

2.2 官方文档和导入

https://beautifulsoup.readthedocs.io/zh-cn/latest/
太好了,是中文文档,我们有救了!!!🤪,而且还有八嘎版和棒子版,可以说很全面了📚
在这里插入图片描述

# pip一下beautiful soup库
pip install bs4

# 使用前先导包
from bs4 import BeautifulSoup

# 创建对象
# 服务器响应文件生成对象
soup = BeautifulSoup(response.read().decode(), "lxml")

# 本地文件生成对象
soup = BeautifulSoup(open("xx.html"), "lxml")

官方给的安装方式是pip install beautifulsoup4,但。。。都能用,能用就行😌,注意⚠️:bs4默认打开文件编码格式是gbk

2.3 节点定位

定位方法 表达式 调用例子 解释
标签名查找
.
soup.a
找到第一个符合条件的数据
.标签.属性值
soup.a.attrs
返回标签属性和属性值
函数
find():
返回第一个符合条件的数据
find("a", tittle="xx")
根据tittle的值,找到对应的标签对象
find("xx", class_="xx")
根据class的值来找到对应的标签对象,class需要加下划线,不然和类定义重名
find_all():
返回一个列表
find_all("xx")
查到所有的xx标签
find_all(["a", "span"])
返回所有的a和span
find_all("a", limit=2)
查询前两个a标签
select():
返回一个列表并且是多个数据
根据选择器找到节点对象
element
soup.select("a")
类选择器 .class
soup.select(".a")
属性选择器 [attribute]
soup.select("li[class]")
属性选择器 [attribute=value]
soup.select("li[class='hengheng']")
id选择器 #id
soup.select("#a")
层级选择器-后代选择器
空格 div p
层级选择器-子代选择器
> div>p
层级选择器-兄弟选择器
div, p

2.4 节点信息

2.4.1 获取节点内容

适用于标签中嵌套标签的结构

  1. obj.string
  2. obj.get_text() 推荐使用

如果标签对象中只有内容,那么string和get_text()都能获取到内容
如果标签对象中还有标签,那么string获取不到内容

2.4.2 节点属性

  1. tag.name 获取标签名
  2. tag.attrs 将属性值作为一个字典返回

2.4.3 获取节点属性

  1. obj.attrs.get(‘tittle’) 常用
  2. obj.get(“tittle”)
  3. obj[“tittle”]

再来一个小例子🌰
1. 使用bs4定位方式,爬取麦当劳商品图片,并保存到本地

import urllib.request
from bs4 import BeautifulSoup as bs
from lxml import etree

def people_message():
    url = 'https://www.mcdonalds.com.cn/index/Food/menu/burger'
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36',
        'Cookie': 'ARRAffinity=1ce7c1f395b5a48d30b8c50f0ca2b9c61b26204706d05327db11e7f501495c8a; ARRAffinitySameSite=1ce7c1f395b5a48d30b8c50f0ca2b9c61b26204706d05327db11e7f501495c8a; _gid=GA1.3.399133088.1750492704; sajssdk_2015_cross_new_user=1; sensorsdata2015jssdkcross=%7B%22distinct_id%22%3A%22197917efc90995-0db57a24b00b2f8-26011e51-1327104-197917efc91daa%22%2C%22%24device_id%22%3A%22197917efc90995-0db57a24b00b2f8-26011e51-1327104-197917efc91daa%22%2C%22props%22%3A%7B%22%24latest_referrer%22%3A%22https%3A%2F%2Fwww.baidu.com%2Flink%22%2C%22%24latest_referrer_host%22%3A%22www.baidu.com%22%2C%22%24latest_traffic_source_type%22%3A%22%E8%87%AA%E7%84%B6%E6%90%9C%E7%B4%A2%E6%B5%81%E9%87%8F%22%2C%22%24latest_search_keyword%22%3A%22%E6%9C%AA%E5%8F%96%E5%88%B0%E5%80%BC%22%7D%7D; _gat_UA-49420844-1=1; _gat_realTime=1; _ga_H0FLFLQXE1=GS2.1.s1750492702$o1$g1$t1750492790$j51$l0$h0; _ga=GA1.1.225713622.1750492703; _ga_ZNNHN4PDZF=GS2.3.s1750492791$o1$g0$t1750492791$j60$l0$h0',
        'referer': 'https://www.mcdonalds.com.cn/index/McD/about/company'
    }

    return url, headers

class GetData:
    def __init__(self, url, headers):
        self.url = url
        self.headers = headers
        self.html = None

    def load_web(self):
        request = urllib.request.Request(url=self.url, headers=self.headers)
        response = urllib.request.urlopen(request)
        self.html = response.read().decode("utf-8")

    def handle_web(self):
        soup = bs(self.html, 'lxml')
        # //div[@class="row"]/div/a/span
        name_list = soup.select("div[class='row'] > div > a > span")
        # tree = etree.HTML(self.html)
        # name_list = tree.xpath("//div[@class='row']/div/a/span")
        for i in range(len(name_list)):
            print(name_list[i].get_text())



if __name__ == '__main__':
    url, headers = people_message()
    get_data = GetData(url, headers)
    get_data.load_web()
    get_data.handle_web()

bs4先总结到这儿,爬虫常用的知识点就这么多,其他的。。。如果用到了再看文档或甩给GPT解决吧

3. re

re 是 Python 的一个内置模块,用于处理正则表达式。正则表达式是一种文本模式,用于描述字符串的结构。借助 re 模块,您可以执行复杂的字符串搜索、匹配和替换等操作。

3.1 re&正则表达式官方文档

https://www.w3cschool.cn/python3/python3-reg-expressions.html
在爬虫中正则表达式经常使用,而且不仅爬虫,在其他方向re正则也是相当重要,所以学好re,也是很必须的。文档是w3c的,感觉清晰明了

3.2 正则表达式模式

  1. 模式字符串使用特殊的语法来表示一个正则表达式:
  2. 字母和数字表示他们自身。一个正则表达式模式中的字母和数字匹配同样的字符串。
  3. 多数字母和数字前加一个反斜杠时会拥有不同的含义。
  4. 标点符号只有被转义时才匹配自身,否则它们表示特殊的含义。
  5. 反斜杠本身需要使用反斜杠转义。
  6. 由于正则表达式通常都包含反斜杠,所以你最好使用原始字符串来表示它们。模式元素(如 r’/t’,等价于’//t’)匹配相应的特殊字符。
  7. 下表列出了正则表达式模式语法中的特殊元素。如果你使用模式的同时提供了可选的标志参数,某些模式元素的含义会改变。
表达式 解释
^
匹配字符串的开头
$
匹配字符串的末尾
.
匹配任意字符,除了换行符
[...]
用来表示一组字符.示例:[amk] 匹配 'a','m'或'k'
[^...]
不在[]中的字符:[^abc] 匹配除了a,b,c之外的字符。
re*
匹配0个或多个的表达式
re+
匹配1个或多个的表达式
re?
匹配0个或1个由前面的正则表达式定义的片段,非贪婪方式
re{n}
匹配n个前面表达式。例如,"o{2}"不能匹配"Bob"中的"o",但是能匹配"food"中的两个o
re{n,}
精确匹配n个前面表达式。例如,"o{2,}"不能匹配"Bob"中的"o",但能匹配"foooood"中的所有o。"o{1,}"等价于"o+"。"o{0,}"则等价于"o*"
re{n, m}
匹配 n 到 m 次由前面的正则表达式定义的片段,贪婪方式
a | b
匹配a或b
(re)
匹配括号内的表达式,也表示一个组
(?imx)
正则表达式包含三种可选标志:i, m, 或 x 。只影响括号中的区域
(?#...)
注释
\w
匹配非特殊字符,即a-z、A-Z、0-9、_、汉字
\W
匹配特殊字符,即非字母、非数字、非汉字、非_
\s
匹配任意空白字符,等价于 [\t\n\r\f]
\S
匹配任意非空字符
\d
匹配任意数字,等价于 [0-9]
\D
匹配任意非数字
\A
匹配字符串开始
\Z
匹配字符串结束,如果是存在换行,只匹配到换行前的结束字符串
\z
匹配字符串结束
\G
匹配最后匹配完成的位置
\n\r\t
匹配一个换行符。匹配一个制表符。等

3.3 正则表达式修饰符

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

3.4 re方法

3.4.1 re.match(pattern, string, flags=0)

尝试从字符串的起始位置匹配一个模式,如果不是起始位置匹配成功的话,match() 就返回 none

  1. pattern: 匹配的正则表达式
  2. string: 要匹配的字符串。
  3. flags: 标志位,用于控制正则表达式的匹配方式,如:是否区分大小写,多行匹配等等。
import re
print(re.match('www', 'www.w3cschool.cn').span())  # 在起始位置匹配
print(re.match('cn', 'www.w3cschool.cn'))         # 不在起始位置匹配

在这里插入图片描述

3.4.2 re.match(pattern, string, flags=0)

扫描整个字符串并返回第一个成功的匹配

  1. pattern: 匹配的正则表达式
  2. string: 要匹配的字符串。
  3. flags: 标志位,用于控制正则表达式的匹配方式,如:是否区分大小写,多行匹配等等。
import re

print(re.search('www', 'www.w3cschool.cn').span())  # 在起始位置匹配
print(re.search('cn', 'www.w3cschool.cn').span())         # 不在起始位置匹配

在这里插入图片描述

3.4.3 re.sub(pattern, repl, string, max=0)

用于替换字符串中的匹配项,返回的字符串是在字符串中用 re 最左边不重复的匹配来替换。如果模式没有发现,字符将被没有改变地返回

import re

phone = "2004-959-559 # 这是一个电话号码"

# 删除注释
num = re.sub(r'#.*$', "", phone)
print ("电话号码 : ", num)

# 移除非数字的内容
num = re.sub(r'\D', "", phone)
print ("电话号码 : ", num)

在这里插入图片描述

网页解析总结到此结束,应该。。。差不多。。。暂时。。。够用了,如果后续还有问题解决不了再说吧👋


网站公告

今日签到

点亮在社区的每一天
去签到