Python
基础语法
01概念
02安装
03变量
04字符串
05数
06常量与注释
07列表
08元组
09if语句
10字典
11集合
12复合数据类型对比
13推导式
14用户输入
15while循环
16函数
17类
18面向对象编程
19文件操作
20异常处理
21日期和时间
22魔术方法
23内置函数
24线程
25并发&并行
26正则表达式
27迭代器
28装饰器
29生成器
30上下文管理器
31函数式编程
32闭包
33解包
34工具库
35连接关系型数据库
36虚拟环境
37异步编程
网络爬虫
01urllib库[了解]
02requests库
03数据交换格式
04解析库
05lxml
06Beautiful Soup
07Xpath语法
08动态网页的处理
-
+
首页
06Beautiful Soup
BeautifulSoup 是 Python 生态中最受欢迎的 **HTML/XML 解析库**,由 Leonard Richardson 开发,旨在简化从非结构化的网页源码中提取数据的过程。 能将复杂的 HTML/XML 文档转换为**可导航的节点树**,通过直观的 API 快速定位和提取标签、属性、文本等信息,是爬虫开发中解析网页内容的核心工具之一。 ## 特点 作为专为数据提取设计的解析库,BeautifulSoup 凭借以下特性成为新手和开发者的首选: **简单易用**:API 设计直观,类似自然语言的语法(如 .find('div')),无需深入学习 XPath 或复杂语法即可上手。 **自动修复不规范 HTML**:网页源码常存在标签未闭合、嵌套混乱等问题,BeautifulSoup 能自动修复这些错误(如补全缺失的 </div>),确保解析正常。 **多解析器支持**:可搭配不同解析器(如 Python 标准库的 html.parser、高性能的 lxml、容错性强的 html5lib),灵活应对不同场景。 **节点树导航**:将文档转换为节点树后,支持通过父子、兄弟关系遍历节点,方便复杂结构的数据提取。 **与爬虫库兼容**:无缝对接 requests、urllib 等网络请求库,形成 “请求 - 解析 - 提取” 的完整爬虫流程。 ## 基本使用 **安装** BeautifulSoup 是第三方库,需通过 pip 安装(当前主流版本为 **BeautifulSoup 4**,包名为 beautifulsoup4): ```python pip install beautifulsoup4 ``` **解析器选择** BeautifulSoup 本身不做解析工作,依赖外部解析器。 常用解析器对比: | 解析器 | 优势 | 劣势 | 安装方式 | | ----------- | ------------------------------------- | -------------------------------- | -------------------- | | html.parser | Python 标准库,无需额外安装,兼容性好 | 速度较慢,对复杂 HTML 容错性一般 | 内置(无需安装) | | lxml | 速度最快,支持 HTML 和 XML,容错性强 | 需要额外安装 | pip install lxml | | html5lib | 模拟浏览器解析,能修复所有不规范 HTML | 速度最慢 | pip install html5lib | **推荐选择**:优先使用 lxml(兼顾速度和容错性),代码中通过 features='lxml' 指定。 **语法:解析字符串(最常用,如爬虫获取的网页源码)**: ```python soup = BeautifulSoup(html_str, '解析器') # html_str 是待解析的HTML/XML字符串 ``` **基本流程** 使用 BeautifulSoup 解析文档的核心步骤: 1. 获取 HTML/XML 内容(如通过 requests 爬取网页源码); 2. 创建 BeautifulSoup 对象(传入内容和解析器); 3. 通过对象的方法 / 属性提取目标数据。 ## 核心对象与节点树 BeautifulSoup 将文档解析为**节点树**,树中的每个元素都是特定类型的对象,核心对象包括: | 对象类型 | 含义 | 示例 | | --------------- | -------------------------------------- | ---------------------------------- | | BeautifulSoup | 整个文档的根对象(代表完整文档) | soup = BeautifulSoup(html, 'lxml') | | Tag | HTML/XML 标签(如 \<div>、\<a>) | soup.find('div') 返回的结果 | | NavigableString | 标签内的文本内容(可导航的字符串) | tag.text 或 tag.string 返回的结果 | | Comment | 文档中的注释(特殊的 NavigableString) | \<!-- 这是注释 --> 解析后的对象 | ```python from bs4 import BeautifulSoup # 示例 HTML 内容 html = """ <html> <body> <div class="container"> <h1>Python 爬虫</h1> <p>BeautifulSoup 是解析利器<!-- 真的很方便 --></p> </div> </body> </html> """ # 创建 BeautifulSoup 对象(根对象) soup = BeautifulSoup(html, 'lxml') # 使用 lxml 解析器 # 1. BeautifulSoup 对象(根对象) print(type(soup)) # <class 'bs4.BeautifulSoup'> # 2. Tag 对象(<h1>标签) h1_tag = soup.find('h1') print(type(h1_tag)) # <class 'bs4.element.Tag'> # 3. NavigableString 对象(<h1>内的文本) h1_text = h1_tag.string print(type(h1_text)) # <class 'bs4.element.NavigableString'> print(h1_text) # Python 爬虫 # 4. Comment 对象(注释内容) p_tag = soup.find('p') comment = list(p_tag.children)[-1] # 获取<p>内的注释 print(type(comment)) # <class 'bs4.element.Comment'> print(comment) # 真的很方便 ``` 输出结果: ```txt <class 'bs4.BeautifulSoup'> <class 'bs4.element.Tag'> <class 'bs4.element.NavigableString'> Python 爬虫 <class 'bs4.element.Comment'> 真的很方便 ``` ## 节点查找与提取 最常用的是 **find()** 和 **find_all()**,配合节点属性和文本筛选,可满足绝大多数场景。 - **find(name, attrs, recursive, text, \**kwargs)**:返回**第一个匹配**的节点(Tag 对象)。 - **find_all(name, attrs, recursive, text, limit,\** kwargs)**:返回**所有匹配**的节点(列表,元素为 Tag 对象)。 关键参数说明: | 参数 | 作用 | 示例 | | -------- | --------------------------------------------- | ------------------------------------------------------------ | | name | 标签名(如 'div'、'a'),可传列表匹配多个标签 | find_all(['h1', 'h2']) 匹配所有\<h1>和\<h2> | | attrs | 标签属性字典(如 {'class': 'title'}) | find(attrs={'id': 'content'}) 匹配id="content"的标签 | | text | 匹配节点文本(支持字符串、正则、列表) | find_all(text='Python') 匹配文本为Python的节点 | | limit | 限制返回结果数量(仅 find_all() 可用) | find_all('a', limit=5) 返回前 5 个\<a> | | **kwargs | 直接传入属性名作为参数(更简洁) | find(class_='title') 等价于 attrs={'class': 'title'}(注意class是关键字,需加_) | 例如: ```python from bs4 import BeautifulSoup # 示例 HTML 内容 html = """ <div class="box"> <h2>热门文章</h2> <ul> <li class="article"><a href="/a1">Python 基础</a></li> <li class="article hot"><a href="/a2">爬虫实战</a></li> <li class="article"><a href="/a3">数据分析</a></li> </ul> </div> """ soup = BeautifulSoup(html, 'lxml') # 1. 按标签名查找 h2_tag = soup.find('h2') # 第一个<h2> print(h2_tag.text) # 热门文章 all_li = soup.find_all('li') # 所有<li> print(len(all_li)) # 3 # 2. 按属性查找(class、id等) # 注意:class是Python关键字,需用class_ hot_li = soup.find('li', class_='hot') # 带class="hot"的<li> print(hot_li.text) # 爬虫实战 # 多属性匹配(同时满足class和标签名) article_li = soup.find_all('li', class_='article') # 所有class="article"的<li> print([li.text for li in article_li]) # ['Python 基础', '爬虫实战', '数据分析'] # 3. 按文本查找 a_with_python = soup.find('a', string='Python 基础') # 文本为'Python 基础'的<a> print(a_with_python['href']) # /a1(提取href属性) # 4. 组合条件查找(标签+属性+文本) target = soup.find('li', class_='article', string=lambda x: '实战' in x) print(target.text) # 爬虫实战 ``` 注意:在高版本中text参数已换为string。 输出结果: ```txt 热门文章 3 爬虫实战 ['Python 基础', '爬虫实战', '数据分析'] /a1 爬虫实战 ``` ## 节点属性与文本提取 找到 Tag 对象后,需提取其属性(如 href、src)或文本内容,核心方法如下 **提取属性** - 通过 tag['属性名'] 访问(类似字典); - 通过 tag.get('属性名') 访问(不存在时返回 None,更安全)。 ```python from bs4 import BeautifulSoup # 示例 HTML 内容 html = """ <div class="box"> <h2>热门文章</h2> <ul> <li class="article"><a href="/a1">Python 基础</a></li> <li class="article hot"><a href="/a2">爬虫实战</a></li> <li class="article"><a href="/a3">数据分析</a></li> </ul> </div> """ soup = BeautifulSoup(html, 'lxml') a_tag = soup.find('a') # 第一个<a>标签 # 提取href属性 print(a_tag['href']) # /a1(若属性不存在会抛KeyError) print(a_tag.get('href')) # /a1(更安全,推荐) print(a_tag.get('target', '_self')) # 不存在时返回默认值_self ``` 输出结果: ```txt /a1 /a1 _self ``` **提取文本** | 方法 / 属性 | 作用 | 示例(以上述\<li>为例) | | ------------------------- | ------------------------------------------ | ---------------------------------------------------- | | tag.text | 返回标签内所有文本的拼接(包括子节点文本) | \<li>\<a>爬虫\</a>实战\</li> 中 li.text 为'爬虫实战' | | tag.get_text(strip=False) | 类似text,但可通过strip=True去除首尾空白 | tag.get_text(strip=True) 去除文本前后空格 | ```python from bs4 import BeautifulSoup # 示例 HTML 内容 html = """ <div class="box"> <h2>热门文章</h2> <ul> <li class="article"><a href="/a1">Python 基础</a></li> <li class="article hot"><a href="/a2"> 爬虫实战</a></li> <li class="article"><a href="/a3">数据分析</a></li> </ul> </div> """ soup = BeautifulSoup(html, 'lxml') li_tag = soup.find('li', class_='hot') # <li class="article hot"><a href="/a2">爬虫实战</a></li> print(li_tag.text) # 爬虫实战(拼接所有子节点文本) print(li_tag.get_text(strip=True)) # 爬虫实战(去除可能的空白) ``` 输出结果: ```txt 爬虫实战 爬虫实战 ``` ## 节点遍历(父子 / 兄弟关系) BeautifulSoup 将节点树视为层级结构,支持通过父子、兄弟关系遍历节点,常用属性: | 方法 / 属性 | 作用 | | -------------------- | ---------------------------------------------- | | tag.children | 迭代器,返回标签的**直接子节点**(不含孙节点) | | tag.descendants | 迭代器,返回标签的**所有后代节点**(子、孙等) | | tag.parent | 返回标签的**直接父节点** | | tag.parents | 迭代器,返回标签的**所有祖先节点** | | tag.next_sibling | 返回标签的**下一个兄弟节点** | | tag.previous_sibling | 返回标签的**上一个兄弟节点** | ```python from bs4 import BeautifulSoup # 示例 HTML 内容 html = """ <div class="box"> <h2>热门文章</h2> <ul> <li class="article"><a href="/a1">Python 基础</a></li> <li class="article hot"><a href="/a2">爬虫实战</a></li> <li class="article"><a href="/a3">数据分析</a></li> </ul> </div> """ soup = BeautifulSoup(html, 'lxml') # 获取<ul>标签(父节点) ul_tag = soup.find('ul') # 1. 遍历直接子节点(<li>标签) for child in ul_tag.children: if child.name is not None: # 过滤空白字符节点 print(child.text) # 输出: # Python 基础 # 爬虫实战 # 数据分析 # 2. 查找子节点的父节点 li_tag = soup.find('li') print(li_tag.parent.name) # ul(<li>的直接父节点是<ul>) # 3. 查找兄弟节点 first_li = soup.find('li') # 第一个<li> second_li = first_li.next_sibling.next_sibling # 下一个兄弟(需跳过空白节点) print(second_li.text) # 爬虫实战 ``` 输出结果: ```txt Python 基础 爬虫实战 数据分析 ul 爬虫实战 ``` 解析:为什么查找兄弟节点时,使用的是second_li = first_li.next_sibling.next_sibling。 first_li.next_sibling 得到空白节点的核心原因是:**HTML 中的空白字符(换行、空格、制表符等)会被解析为独立的文本节点**,而这些节点会被算作 “兄弟节点” 的一部分。 html格式化后的结构如下: ```html <ul> <li class="article"><a href="/a1">Python 基础</a></li> <!-- 第一个li --> \n <!-- 换行+空格:空白字符 --> <li class="article hot"><a href="/a2">爬虫实战</a></li> <!-- 第二个li --> \n <!-- 换行+空格:空白字符 --> <li class="article"><a href="/a3">数据分析</a></li> <!-- 第三个li --> </ul> ``` 当 BeautifulSoup 用 lxml 解析时,会将整个文档转换为**节点树**,其中: - \<li class="article"> 是第一个标签节点(first_li); - 第一个 \<li> 后面的 \n (换行 + 空格)会被解析为一个**文本节点**(NavigableString 类型),内容是空白; - 这个空白文本节点的 “下一个兄弟” 才是第二个 \<li> 标签节点。 next_sibling 的作用是返回当前节点**紧随其后的同级节点**(无论节点类型是标签、文本还是注释)。在你的示例中: - first_li 是第一个 \<li> 标签节点; - first_li.next_sibling 指向的是第一个 \<li> 后面的**空白文本节点**(\n ); - 再调用一次 next_sibling(即 first_li.next_sibling.next_sibling),才会跳过空白节点,指向第二个 \<li> 标签节点。 ## 常用语法表 | 操作目标 | 语法示例 | 返回值类型 | | ------------------ | ------------------------------------ | ------------------ | | 初始化解析对象 | soup = BeautifulSoup(html, 'lxml') | BeautifulSoup 对象 | | 查找第一个\<div> | soup.find('div') | Tag 或 None | | 查找所有\<p> | soup.find_all('p') | 列表(Tag 元素) | | 按 class 查找 | soup.find_all('p', class_='content') | 列表(Tag 元素) | | 按 CSS 选择器查找 | soup.select('div.container > p') | 列表(Tag 元素) | | 提取标签文本 | tag.text 或 tag.get_text(strip=True) | 字符串 | | 提取 href 属性 | tag.get('href') | 字符串或 None | | 查找父节点 | tag.parent | Tag 或 None | | 查找下一个兄弟标签 | tag.find_next_sibling('li') | Tag 或 None | ## 注意事项 **解析器选择**:优先使用 lxml 解析器(速度快 + 容错性强),避免因解析器导致的提取错误。 **class 属性的特殊性**:HTML 中 class 是关键字,在 BeautifulSoup 中需用 class_ 表示(末尾加下划线),或通过 attrs={'class': 'xxx'} 传入。 **空白节点处理**:HTML 中的换行、空格会被解析为 NavigableString 节点,遍历兄弟 / 子节点时需注意过滤(如 if child.name is not None)。 **动态内容限制**:BeautifulSoup 只能解析**静态 HTML**(服务器返回的源码),无法处理 JavaScript 动态生成的内容(如通过 AJAX 加载的数据),此类场景需配合 Selenium 或分析 API 接口。 **性能优化**:处理超大 HTML 文档时,可通过 lxml 解析器提升速度,或使用 find() 而非 find_all() 减少内存占用。 ## 案例 ### 解析 HTML 页面(提取文章列表) ```python from bs4 import BeautifulSoup # 1. 定义HTML内容(同上) html_content = """ <!DOCTYPE html> <html> <head> <title>技术文章列表</title> </head> <body> <div class="container"> <h1>热门技术文章</h1> <ul class="articles"> <li class="article"> <a href="/python-basic" class="link">Python 基础入门</a> <span class="views">10000+ 阅读</span> </li> <li class="article hot"> <a href="/xpath-tutorial" class="link">XPath 语法详解</a> <span class="views">25000+ 阅读</span> </li> <li class="article"> <a href="/lxml-guide" class="link">lxml 库使用指南</a> <span class="views">8000+ 阅读</span> </li> </ul> </div> </body> </html> """ # 2. 解析HTML(使用lxml解析器) soup = BeautifulSoup(html_content, 'lxml') # 3. 使用Beautiful Soup提取数据 print("===== 提取页面标题 =====") title = soup.find('title').get_text() # 等价于 .text print(title) # 技术文章列表 print("\n===== 提取所有文章标题 =====") # 找到ul.articles下的所有a标签 article_links = soup.find('ul', class_='articles').find_all('a') for link in article_links: print(link.get_text()) print("\n===== 提取所有文章链接 =====") # 直接通过a.link筛选 all_links = soup.find_all('a', class_='link') for link in all_links: print(link.get('href')) # 获取href属性 print("\n===== 提取热门文章(带hot类) =====") # 筛选同时包含article和hot类的li hot_li = soup.find('li', class_=lambda c: c and 'article' in c and 'hot' in c) hot_title = hot_li.find('a').get_text() hot_views = hot_li.find('span', class_='views').get_text() print(f"热门文章:{hot_title},阅读量:{hot_views}") print("\n===== 提取阅读量超过10000的文章 =====") # 遍历所有article项 articles = soup.find_all('li', class_='article') for article in articles: title = article.find('a').get_text() views = article.find('span', class_='views').get_text() # 提取数字部分并比较 if int(views.replace('+', '').split()[0]) > 10000: print(title, views) ``` 输出结果: ```txt ===== 提取页面标题 ===== 技术文章列表 ===== 提取所有文章标题 ===== Python 基础入门 XPath 语法详解 lxml 库使用指南 ===== 提取所有文章链接 ===== /python-basic /xpath-tutorial /lxml-guide ===== 提取热门文章(带hot类) ===== 热门文章:XPath 语法详解,阅读量:25000+ 阅读 ===== 提取阅读量超过10000的文章 ===== XPath 语法详解 25000+ 阅读 ``` ### 解析 XML 文档(提取书籍信息) ```python from bs4 import BeautifulSoup # 1. 定义XML内容(同上) xml_content = """ <bookstore> <book category="编程"> <title lang="zh">Python 编程入门</title> <author>张三</author> <price>59.9</price> <publish_date>2023-01-15</publish_date> </book> <book category="数据科学"> <title lang="en">Data Analysis with Python</title> <author>李四</author> <price>79.0</price> <publish_date>2022-05-20</publish_date> </book> <book category="编程"> <title lang="zh">JavaScript 高级程序设计</title> <author>王五</author> <price>109.0</price> <publish_date>2021-11-30</publish_date> </book> </bookstore> """ # 2. 解析XML(Beautiful Soup对XML的支持稍弱,需指定解析器) soup = BeautifulSoup(xml_content, 'lxml-xml') # 注意用lxml-xml解析器 # 3. 使用Beautiful Soup提取数据 print("===== 提取所有书籍标题 =====") titles = soup.find_all('title') for title in titles: print(title.get_text()) print("\n===== 提取所有编程类书籍 =====") # 筛选category为"编程"的book标签 programming_books = soup.find_all('book', category='编程') for book in programming_books: print(book.find('title').get_text()) print("\n===== 提取中文书籍(lang属性为zh) =====") chinese_titles = soup.find_all('title', lang='zh') for title in chinese_titles: print(title.get_text()) print("\n===== 提取价格大于70的书籍信息 =====") expensive_books = [] for book in soup.find_all('book'): price = float(book.find('price').get_text()) if price > 70: title = book.find('title').get_text() author = book.find('author').get_text() expensive_books.append(f"{title} - {author} - {price}元") for book in expensive_books: print(book) print("\n===== 提取最后一本书的出版日期 =====") # 找到所有book,取最后一个 last_book = soup.find_all('book')[-1] last_date = last_book.find('publish_date').get_text() print(last_date) ``` 输出结果: ```txt ===== 提取所有书籍标题 ===== Python 编程入门 Data Analysis with Python JavaScript 高级程序设计 ===== 提取所有编程类书籍 ===== Python 编程入门 JavaScript 高级程序设计 ===== 提取中文书籍(lang属性为zh) ===== Python 编程入门 JavaScript 高级程序设计 ===== 提取价格大于70的书籍信息 ===== Data Analysis with Python - 李四 - 79.0元 JavaScript 高级程序设计 - 王五 - 109.0元 ===== 提取最后一本书的出版日期 ===== 2021-11-30 ``` ### 解析复杂导航菜单(轴选择器对应实现) ```python from bs4 import BeautifulSoup # 1. 定义HTML内容(同上) html_content = """ <div class="nav"> <ul> <li id="home">首页</li> <li id="products">产品 <ul class="submenu"> <li>电脑</li> <li>手机</li> <li>平板</li> </ul> </li> <li id="about">关于我们</li> <li id="contact">联系我们</li> </ul> </div> """ # 2. 解析HTML soup = BeautifulSoup(html_content, 'lxml') # 3. 使用Beautiful Soup提取数据(对应轴选择器功能) print("===== 提取所有子菜单(后代节点) =====") # 找到id=products的li,再找所有后代li(递归查找) products_li = soup.find('li', id='products') submenu_items = products_li.find_all('li', recursive=True) # recursive=True默认递归所有后代 for item in submenu_items: print(item.get_text(strip=True)) # strip=True去除空白 print("\n===== 提取关于我们的前一个兄弟节点 =====") # 找到id=about的li,获取前一个兄弟li(自动跳过空白节点) about_li = soup.find('li', id='about') prev_sibling = about_li.find_previous_sibling('li') print(prev_sibling.get_text(strip=True)) # 产品 print("\n===== 提取首页的所有后续兄弟节点 =====") home_li = soup.find('li', id='home') # 获取所有后续兄弟li(find_next_siblings) next_siblings = home_li.find_next_siblings('li') for sibling in next_siblings: print(sibling.get_text(strip=True)) print("\n===== 提取子菜单的所有祖先节点 =====") # 找到class=submenu的ul,获取所有祖先节点的id和class submenu_ul = soup.find('ul', class_='submenu') ancestors = submenu_ul.find_parents() # 获取所有祖先(不包含自身) # 提取祖先节点的id和class属性 ancestor_attrs = [] for ancestor in ancestors: if 'id' in ancestor.attrs: ancestor_attrs.append(ancestor['id']) if 'class' in ancestor.attrs: ancestor_attrs.extend(ancestor['class']) # class可能是列表 print(ancestor_attrs) # ['products', 'nav'] ``` 输出结果: ```txt ===== 提取所有子菜单(后代节点) ===== 电脑 手机 平板 ===== 提取关于我们的前一个兄弟节点 ===== 产品电脑手机平板 ===== 提取首页的所有后续兄弟节点 ===== 产品电脑手机平板 关于我们 联系我们 ===== 提取子菜单的所有祖先节点 ===== ['products', 'nav'] ```
毛林
2025年9月7日 12:11
转发文档
收藏文档
上一篇
下一篇
手机扫码
复制链接
手机扫一扫转发分享
复制链接
Markdown文件
PDF文档(打印)
分享
链接
类型
密码
更新密码