)
前言标题里的「(四)」说明这是某个系列教程的第四篇。本文是自足的不依赖前面几篇的内容也不做任何跨篇引用你从这一篇开始读完全没问题。如果你是从系列中途进来直接看下去就行。这一篇讲BeautifulSoup的入门用法覆盖四件事怎么装、四种解析器有什么区别、怎么查找节点、怎么在树里上下遍历。BeautifulSoup常简称 bs4是 Python 里最常用的 HTML 解析库之一它的卖点是API 顺手——你不用记 XPath 语法用近乎自然语言的方式就能从网页里把内容抠出来。前置说明本文代码为逐行推演未经运行本机没有 Python 解释器也装不了第三方库。BeautifulSoup是第三方库其具体参数与行为请以该库官方文档为准。爬虫合规要求照旧只取公开页面、遵守 robots.txt、带可识别 User-Agent、限速不绕登录墙与验证码。再补一句版本背景BeautifulSoup早期版本同时支持 Python 2 和 Python 3但Python 2.7 已于 2020 年 1 月 1 日停止维护本文的代码与示例一律按 Python 3 给出。一、安装与第一个例子BeautifulSoup的安装包名和导入名不一样这是第一个容易搞混的点pip install beautifulsoup4# 适用于 Python 3.8装了 beautifulsoup4 才能跑from bs4 import BeautifulSoup # 注意导入名是 bs4不是 beautifulsoup4html htmlbodyh1标题/h1p第一段/pp第二段/p/body/htmlsoup BeautifulSoup(html, html.parser)print(soup.h1) # 用属性方式访问第一个 h1print(soup.h1.text) # 标题print(soup.find_all(p)) # 两个 p 标签soup这个对象就是整棵树的根soup.h1、soup.body这类属性访问会返回第一个匹配的标签没有就返回None。这很方便但也埋着一个坑属性名可能和 Python 关键字冲突比如soup.class不合法class是关键字得改用soup.find(class_...)这种带下划线的写法或者通过attrs字典取。另外soup.title只返回第一个title想要全部得用find_all。二、四种解析器的区别BeautifulSoup(html, 解析器名)的第二个参数决定用哪个底层解析器。BeautifulSoup本身不做解析它只是把 HTML 交给某个解析器再给你一套统一的查询接口。所以选错解析器得到的结果可能不一样。解析器名来源特点主要缺点html.parserPython 标准库无需额外安装速度一般容错不如浏览器lxml第三方 lxml速度快容错好需要 lxml依赖 C 库lxml-xml第三方 lxml按 XML 严格解析只用于 XML 文档html5lib第三方 html5lib完全按浏览器方式解析最宽容纯 Python 实现较慢选择建议不想装任何东西用html.parser。追求解析速度、且能装 lxml用lxml。文档破损严重、要求结果和浏览器一致用html5lib。解析的是 XML 而不是 HTML用lxml-xml。一个重要事实对同一份非法 HTML不同解析器会生成不同的树。所以项目里应当显式指定解析器不要依赖默认值——BeautifulSoup不指定时会给出警告正是因为不同环境下默认解析器可能不同。三、查找find / find_all / select3.1find与find_allfind(name, attrs, ...)返回第一个匹配的标签没找到返回None。find_all(name, attrs, ...)返回所有匹配的标签一个列表式对象。# 适用于 Python 3.8需安装 beautifulsoup4soup BeautifulSoup(HTML, html.parser)soup.find(p) # 第一个 psoup.find_all(p) # 所有 psoup.find(div, idmain) # 按 id 属性soup.find_all(a, class_link) # 按 class注意下划线soup.find_all(a, hrefTrue) # 只要有 href 属性的 asoup.find_all([h1, h2]) # 同时找多种标签三个细节class_带下划线。因为class是 Python 关键字BeautifulSoup约定用class_代替。hrefTrue表示「只要这个属性存在」要精确匹配值就写href/about。第一个参数可以传列表一次匹配多种标签名。3.2select用 CSS 选择器如果你熟悉 CSSselect会更顺手# 适用于 Python 3.8soup.select(div#main p.title) # 后代选择器soup.select(a[href]) # 带 href 的 asoup.select(ul li) # 直接子元素soup.select(.article .body) # 类名组合soup.select_one(h1) # 只取第一个等价于 findselect返回列表select_one返回第一个或None。CSS 选择器对类名组合的表达比find_all更简洁但能力略弱于 XPath比如不能按文本内容筛选。3.3 取文本与属性# 适用于 Python 3.8tag soup.find(a, class_link)tag.get_text() # 取标签内所有文本拼成一个字符串tag.get_text(stripTrue) # 顺便去掉首尾空白tag[href] # 取属性不存在会抛 KeyErrortag.get(href) # 取属性不存在返回 None更安全tag.attrs # 属性字典get_text()会把所有后代的文本拼起来这一点和「只取直接文本」不一样。想要只取直接文本可以用tag.string但它在有多个子节点时返回None。四、遍历树children / parents / next_sibling除了自上而下查找BeautifulSoup还能沿树上下左右移动属性 / 方法方向说明.contents向下直接子节点的列表.children向下直接子节点是生成器.descendants向下所有后代生成器.parent向上直接父节点.parents向上所有祖先生成器.next_sibling横向下一个兄弟节点.previous_sibling横向上一个兄弟节点# 适用于 Python 3.8需安装 beautifulsoup4html diva1/ab2/bc3/c/divsoup BeautifulSoup(html, html.parser)div soup.divprint(len(div.contents)) # 3for child in div.children: # 生成器遍历一次就空了print(child.name)b soup.bprint(b.next_sibling.name) # cprint(b.previous_sibling.name) # aprint([p.name for p in b.parents]) # [div, [document]]三个易错点.children/.parents/descendants都是生成器遍历一次就消耗完了要反复用就先list(...)存起来.next_sibling拿到的可能是空白文本节点而不是标签——HTML 里标签之间的换行缩进也算节点所以要判断.name再取parents最后会走到[document]整个文档对象不是None循环时要注意终止条件。常见坑点安装名和导入名搞混❌pip install bs4后写import beautifulsoup4。✅ 安装beautifulsoup4导入from bs4 import BeautifulSoup。class写成关键字❌soup.find_all(a, classlink)——class是关键字直接语法错误。✅soup.find_all(a, class_link)带下划线。不指定解析器❌BeautifulSoup(html)不传第二个参数不同环境解析结果可能不同。✅ 显式写BeautifulSoup(html, html.parser)或你的目标解析器。拿find的结果直接取属性❌soup.find(a)[href]没找到时是None取下标抛TypeError。✅ 先判断是否为None或用soup.find(a)配合get(href)。get_text()期望只取本节点文本❌ 以为tag.get_text()只给直接文本结果把后代文字全拼进来了。✅ 要直接文本用tag.string注意多子节点时为None。把.children当列表反复用❌div.children遍历一次后再遍历什么都不剩。✅ 需要多次访问就list(div.children)。next_sibling拿到的是空白文本节点❌b.next_sibling.text报AttributeError因为拿到的是换行空白。✅ 判断node.name或改用.find_next_sibling(c)。总结主题要点安装pip install beautifulsoup4导入from bs4 import BeautifulSoup解析器四种html.parser/lxml/lxml-xml/html5lib务必显式指定查找find取第一个find_all取全部select用 CSS 选择器属性class要写成class_取值用get()更安全取文本get_text()含所有后代文本遍历children/parents是生成器兄弟节点可能是空白文本BeautifulSoup入门的关键是记住它「不解析、只管查」的定位解析交给底层解析器它给你一套顺手的查询和遍历接口。把find_all、select、get_text和几个遍历属性用熟日常抓取的大部分场景就够了。这一篇是自足的不依赖系列中的其他任何一篇。