3、HTML与网页结构解析:从标签到数据提取
做社交媒体数据抓取,说白了就是跟网页打交道。你想想看,微博、知乎、小红书这些平台,展示给你的内容本质上都是HTML。我刚开始做爬虫那会儿,总觉得HTML就是一堆乱七八糟的标签,后来才明白——不懂HTML结构,你连数据在哪儿都找不到。
这一章,我带你把HTML解析这件事彻底搞明白。从基础标签到DOM树,再到用Python的BeautifulSoup库提取数据,一步到位。
3.1 HTML基础标签:网页的骨架
HTML不是什么高深的东西。它就像搭积木,每个标签就是一块积木。我习惯把HTML标签分成三类:结构标签、内容标签、属性标签。
先看最常用的几个:
| 标签 | 作用 | 我在项目中踩过的坑 |
|---|---|---|
<div> |
块级容器,最常用的布局元素 | 别滥用div,否则页面层级深到你怀疑人生 |
<span> |
行内容器,用于包裹小段文本 | 它不会换行,很多人误以为它能当div用 |
<a> |
超链接,href属性指定目标URL | 注意href可能是相对路径,需要拼接 |
<img> |
图片,src属性指定图片地址 | 有些网站用data-src做懒加载,直接拿src拿不到 |
<p> |
段落文本 | 里面别再套块级元素,否则渲染会出问题 |
3.2 DOM树结构:网页的家族图谱
HTML文档解析后,浏览器会把它变成一棵树——DOM树。每个标签都是一个节点,节点之间有父子、兄弟关系。
举个例子,下面这段HTML:
<html>
<body>
<div id="content">
<h1>标题</h1>
<p class="desc">描述文本</p>
</div>
</body>
</html>
对应的DOM树长这样:
为什么要理解DOM树?因为数据提取的本质就是在树上找节点。你想拿标题,就得从html→body→div→h1这条路径找下去。
3.3 使用BeautifulSoup解析HTML
Python里解析HTML,我首选BeautifulSoup。它能把HTML字符串变成一棵可遍历的树,然后你用各种方法去摘果子(提取数据)。
先安装:
pip install beautifulsoup4 lxml
我习惯用lxml作为解析器,速度比Python内置的html.parser快不少。
基本用法:
from bs4 import BeautifulSoup
html = """
<div class="post">
<h2>今天天气真好</h2>
<p class="author">小明</p>
<span class="likes">128</span>
</div>
"""
soup = BeautifulSoup(html, 'lxml')
# 提取标题
title = soup.find('h2').text
print(title) # 输出:今天天气真好
# 提取作者
author = soup.find('p', class_='author').text
print(author) # 输出:小明
# 提取点赞数
likes = soup.find('span', class_='likes').text
print(likes) # 输出:128
常用的查找方法就这几个:
find()— 找第一个匹配的节点find_all()— 找所有匹配的节点,返回列表select()— 用CSS选择器查找,后面会讲.text— 获取节点内的纯文本['属性名']— 获取节点的属性值,比如a['href']
find_all()配合标签名和class,一步到位。
3.4 CSS选择器入门:精准定位数据
CSS选择器本来是给前端做样式用的,但用在数据提取上,简直不要太爽。BeautifulSoup的select()方法支持CSS选择器,语法简洁,定位精准。
常用的CSS选择器:
| 选择器 | 含义 | 示例 |
|---|---|---|
标签名 |
选择所有该标签 | soup.select('div') |
.类名 |
选择所有该class的元素 | soup.select('.post') |
#id值 |
选择该id的元素 | soup.select('#content') |
父 > 子 |
直接子元素 | soup.select('div > p') |
祖先 后代 |
任意后代元素 | soup.select('div p') |
[属性=值] |
按属性筛选 | soup.select('[data-id="123"]') |
看个实际例子。假设你要抓微博的帖子列表:
html = """
<div class="weibo-feed">
<div class="card" data-id="1001">
<p class="text">今天好开心</p>
<span class="time">10分钟前</span>
</div>
<div class="card" data-id="1002">
<p class="text">下雨了...</p>
<span class="time">20分钟前</span>
</div>
</div>
"""
soup = BeautifulSoup(html, 'lxml')
# 提取所有帖子的文本
texts = soup.select('.card .text')
for t in texts:
print(t.text)
# 提取特定id的帖子
card = soup.select('[data-id="1002"]')
if card:
print(card[0].select_one('.text').text)
div.card > p.text:first-child,这种组合写法在复杂页面里特别管用。你可以在浏览器控制台先测试选择器,确认能选中目标再写代码。
3.5 实战:解析一条微博数据
把上面学的串起来,写一个完整的解析函数:
def parse_weibo(html):
"""
解析单条微博的HTML,返回结构化数据
"""
soup = BeautifulSoup(html, 'lxml')
# 提取用户信息
user = soup.select_one('.user-name')
username = user.text.strip() if user else '未知'
# 提取发布时间
time = soup.select_one('.time')
pub_time = time.text.strip() if time else ''
# 提取正文
content = soup.select_one('.content p')
text = content.text.strip() if content else ''
# 提取互动数据
likes = soup.select_one('.likes span')
like_count = likes.text.strip() if likes else '0'
return {
'username': username,
'time': pub_time,
'text': text,
'likes': like_count
}
嗯,这里要注意——实际抓取时,HTML结构可能比这复杂得多。我遇到过class名是动态生成的,也遇到过数据藏在JavaScript变量里的情况。但不管怎么变,解析的核心思路不变:找到DOM树中的目标节点,提取数据。
select('[class*="content"]')这种模糊匹配。
HTML解析这件事,说白了就是定位+提取两个步骤。定位靠CSS选择器或find方法,提取靠.text或属性访问。多练几次,你就能一眼看出数据在DOM树的哪个位置。
我刚开始做爬虫时,光解析一个页面就要折腾半天。现在熟练了,基本看一眼HTML结构,心里就有数了。你也一样,多写多练,很快就能上手。