楼层: 首页/ 软件技术/ Python 爬虫技术/ 网页解析:BS4 / lxml / 正则
3

网页解析:BS4 / lxml / 正则

Parsing HTML

拿到 HTML 字符串只是第一步,怎么从一堆标签里精准抠出你要的内容?这章讲三个工具:BeautifulSoup(入门友好)、lxml/XPath(快且强)、正则(处理文本)。

BeautifulSoup4 基础

pip install beautifulsoup4 lxml from bs4 import BeautifulSoup html = '<div class="item"><h2 class="title">Python入门</h2><p>作者:小明</p></div>' soup = BeautifulSoup(html, "lxml") # 用 lxml 解析器,比 html.parser 快 # find:找第一个匹配的 title = soup.find("h2", class_="title") print(title.text) # Python入门 # find_all:找所有 for p in soup.find_all("p"): print(p.text) # CSS 选择器 select:更灵活 soup.select("div.item h2.title") # 后代选择器 soup.select("a[href='xxx']") # 属性选择器

标签对象的常用属性

属性拿什么
.text / .get_text()标签里所有文本(含子标签)
.name标签名("div")
.attrs所有属性字典
tag["href"]单个属性
.parent / .parents父节点
.children / .contents子节点
.find_next_sibling()下一个兄弟

遍历 HTML 树

# 从一个标签出发,往上/下/旁边走 soup.find("a").parent # 父标签 soup.find("div").contents # 直接子节点列表(含文本节点) soup.find("div").children # 子节点迭代器 soup.find("div").descendants # 所有子孙 elem.find_next_sibling() # 下一个兄弟 elem.find_previous_sibling() # 上一个兄弟 # decompose() 删标签,extract() 摘标签 soup.find("script").decompose() # 把 script 删掉

常用 CSS 选择器速查

选择器选什么
.itemclass 为 item 的所有标签
#id1id 为 id1
div.itemclass 为 item 的 div
div > adiv 的直接子 a
div adiv 里所有 a(含孙子)
a[href]带 href 属性的 a
a[href*="detail"]href 含 detail
.item:nth-child(2)第二个 .item

lxml 与 XPath

论XPath 比 BS4 快

lxml 是 C 写的解析器,比 BS4 纯 Python 快几倍。XPath 是"路径表达式",像文件系统路径一样定位节点://div[@class="item"]/h2/text()。大规模爬虫用 lxml + XPath,小脚本用 BS4 够了。

from lxml import etree html = '<div><ul><li>A</li><li>B</li></ul></div>' tree = etree.HTML(html) # // 所有子孙,@ 属性,text() 文本,[] 谓语 items = tree.xpath("//li/text()") print(items) # ['A', 'B'] titles = tree.xpath("//div[@class='item']/h2/text()") links = tree.xpath("//a/@href") # 取属性

XPath 常用语法速查

XPath含义
//div所有 div
//div[@class="item"]class 为 item 的 div
//a/@href所有 a 的 href 属性
//h2/text()所有 h2 的文本
//li[1]第一个 li(从1开始)
//a[contains(@href, "detail")]href 含 detail 的 a
//div[position()<=3]前三个 div
//*[@class]所有带 class 属性的标签

正则表达式 re

import re text = "电话:13812345678,备用:13987654321" # findall:找所有匹配 phones = re.findall(r"1\d{10}", text) print(phones) # ['13812345678', '13987654321'] # search:找第一个 m = re.search(r"(\d{3})-(\d{4})", "电话 021-1234") if m: print(m.group(1), m.group(2)) # 021 1234 # 常用元字符:.任意 *重复0+ +重复1+ ?0或1 []字符集 ()分组 \d数字 \w字母数字 \s空白 # 贪婪 vs 非贪婪:.*? 比 .* 少匹配,爬虫里几乎都用 .*?

常用正则模式速查

目标正则
手机号1[3-9]\d{9}
邮箱[\w.-]+@[\w.-]+\.\w+
URLhttps?://[^\s"<>]+
价格¥?\d+\.?\d*
数字\d+(\.\d+)?
中文[\u4e00-\u9fa5]+

JSON 解析:现在网站多是 JSON 接口

import requests, json # 很多网站数据藏在 JSON 接口里,比 HTML 好爬多了 resp = requests.get("https://api.example.com/news") data = resp.json() # 直接转 Python dict/list for item in data["articles"]: print(item["title"], item["url"])

完整案例:爬新闻网站文章列表

# news_spider.py —— BS4 + lxml 解析新闻列表 import requests from bs4 import BeautifulSoup headers = {"User-Agent": "Mozilla/5.0 ..."} resp = requests.get("https://news.example.com/", headers=headers) soup = BeautifulSoup(resp.text, "lxml") articles = [] for item in soup.select(".news-item"): title = item.select_one(".title a").text.strip() link = item.select_one(".title a")["href"] date = item.select_one(".date").text summary = item.select_one(".summary").text.strip() articles.append({"title": title, "link": link, "date": date, "summary": summary}) for a in articles: print(f"{a['date']} {a['title']}")
本章面试题 · 网页解析

1.(概念题)BeautifulSoup 的 find() 和 select() 有什么区别?

查看答案

答案:find() 按标签名+属性找,简单直观;select() 用 CSS 选择器,能写复杂层级。功能上差不多,select 更灵活,find 更易读。

2.(概念题)XPath 的 // 和 / 有什么区别?

查看答案

答案:/ 只找直接子节点,// 找所有子孙节点。爬虫里几乎都用 //,因为你不知道层级有多深。

3.(概念题)什么时候用正则,什么时候用解析库?

查看答案

答案:HTML 结构用 BS4/lxml,正则用来提取文本里的特定模式(电话、邮箱、价格)。别用正则解析 HTML——标签嵌套会把你逼疯。

4.(思考题)为什么现在很多网站"爬 JSON 接口比爬 HTML 容易"?

查看答案

答案:现代网站前端框架渲染,HTML 里经常没数据,数据靠 JS 调 JSON 接口填充。找到那个接口直接调,省得解析 HTML 还能跳过前端反爬。