前言
静态网页是指内容固定、由服务器直接返回 HTML 源码的网页,其数据直接嵌入在源码中,无需动态加载,是爬虫入门的最佳实践场景。掌握静态网页的爬取流程,能帮助开发者建立完整的爬虫开发思维,为后续处理动态网页、反爬网页奠定基础。本文将以 **豆瓣读书 Top250** 为实战目标,从需求分析、请求发送、数据提取到数据清洗、结果输出,完整演示静态网页爬取的全流程,拆解每个环节的核心技巧与注意事项。
摘要
本文聚焦静态网页爬取的全流程实战,以豆瓣读书 Top250 为爬取目标,系统讲解 “需求分析→请求构造→响应解析→数据提取→数据清洗→结果输出” 六大核心环节,结合 urllib 库、正则表达式、BeautifulSoup 等工具实现静态网页数据的完整爬取。文中包含全流程代码示例、数据提取对比表格、输出结果解析,旨在帮助读者掌握静态网页爬取的标准化流程,理解各环节的核心逻辑与实操技巧。
一、静态网页爬取核心流程梳理
静态网页爬取的核心逻辑遵循 “请求 - 解析 - 提取 - 输出” 的标准化流程,各环节职责明确:
| 流程环节 | 核心目标 | 常用工具 / 技术 | 关键注意事项 |
|---|---|---|---|
| 需求分析 | 明确爬取目标、数据字段、输出格式 | 人工分析网页结构 | 确认数据是否在 HTML 源码中(静态特征) |
| 请求构造 | 发送合法请求,获取完整 HTML 源码 | urllib/requests、请求头构造 | 模拟浏览器请求,避免被反爬拦截 |
| 响应解析 | 将字节流响应转为可解析的字符串 | 字符编码处理(utf-8/gbk) | 匹配网页实际编码,避免乱码 |
| 数据提取 | 从 HTML 源码中提取目标数据 | 正则表达式、BeautifulSoup、XPath | 精准匹配数据所在的标签 / 属性 |
| 数据清洗 | 去除冗余数据、格式化字段 | 字符串处理、数据类型转换 | 统一数据格式,提升数据可用性 |
| 结果输出 | 将清洗后的数据保存 / 展示 | 控制台输出、TXT/CSV/Excel 保存 | 按需求选择输出格式,便于后续使用 |
1.1 静态网页特征识别
判断目标网页是否为静态网页的核心方法:
- 打开目标网页,按
F12进入开发者工具,切换至Elements标签,直接搜索关键数据(如书名、评分),若能在 HTML 源码中找到,即为静态网页; - 切换至
Network标签,刷新页面后仅需加载 HTML 文件即可显示完整内容,无额外的 XHR/JSON 请求; - 禁用 JavaScript 后(开发者工具
Settings→Debugger→Disable JavaScript),页面内容仍完整显示。
二、实战目标分析:豆瓣读书 Top250
2.1 爬取需求
- 目标网站:豆瓣读书 Top250
- 爬取字段:排名、书名、作者、出版社、出版时间、评分、简介
- 输出格式:控制台结构化输出 + 文本文件保存
- 爬取范围:豆瓣读书 Top250 第一页数据(演示核心流程)
2.2 网页结构分析
通过浏览器开发者工具分析豆瓣读书 Top250 页面结构:
- 每本图书的信息包裹在
class="item"的 li 标签中; - 排名:
class="pic"下的 em 标签; - 书名:
class="pl2"下的 a 标签(title 属性); - 作者 / 出版社 / 出版时间:
class="pl"的 span 标签; - 评分:
class="rating_nums"的 span 标签; - 简介:
class="inq"的 span 标签(部分图书无简介)。
三、全流程代码实现与解析
3.1 环境准备
需安装 BeautifulSoup(用于 HTML 解析):
bash
运行
pip install beautifulsoup43.2 完整爬取代码
python
运行
import urllib.request import urllib.error from bs4 import BeautifulSoup import re # ===================== 1. 配置基础参数 ===================== # 目标URL url = "https://book.douban.com/top250" # 请求头(模拟Chrome浏览器) headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8", "Referer": "https://book.douban.com/" } # ===================== 2. 发送请求,获取响应 ===================== def get_html(url, headers): """ 发送HTTP请求,获取网页HTML源码 :param url: 目标URL :param headers: 请求头 :return: 解码后的HTML字符串(失败返回None) """ try: # 构造请求对象 request = urllib.request.Request(url=url, headers=headers, method="GET") # 发送请求 response = urllib.request.urlopen(request, timeout=15) # 读取响应字节流,解码为字符串(豆瓣读书编码为utf-8) html = response.read().decode("utf-8") print(f"请求成功,响应状态码:{response.getcode()}") return html except urllib.error.HTTPError as e: print(f"HTTP错误:状态码 {e.code},原因 {e.reason}") return None except urllib.error.URLError as e: print(f"网络错误:{e.reason}") return None except Exception as e: print(f"未知错误:{e}") return None # ===================== 3. 解析HTML,提取数据 ===================== def extract_data(html): """ 解析HTML源码,提取目标数据 :param html: 网页HTML字符串 :return: 提取后的字典列表 """ if not html: return [] # 初始化BeautifulSoup对象(使用lxml解析器,需提前安装:pip install lxml) soup = BeautifulSoup(html, "lxml") # 定位所有图书项 book_items = soup.find_all("li", class_="item") # 存储提取的数据 book_list = [] # 遍历每个图书项,提取字段 for item in book_items: book_info = {} try: # 1. 排名 book_info["排名"] = item.find("em").text.strip() # 2. 书名(优先取title属性,无则取文本) title_tag = item.find("span", class_="title") book_info["书名"] = title_tag.get("title", title_tag.text.strip()) # 3. 作者/出版社/出版时间(统一提取后拆分) pl_tag = item.find("p", class_="pl") pl_text = pl_tag.text.strip() # 拆分规则:作者 / 出版社 / 出版时间 / 定价(部分有定价,需兼容) pl_parts = pl_text.split("/") book_info["作者"] = pl_parts[0].strip() if len(pl_parts) > 0 else "" book_info["出版社"] = pl_parts[-3].strip() if len(pl_parts) >= 3 else "" book_info["出版时间"] = pl_parts[-2].strip() if len(pl_parts) >= 2 else "" # 4. 评分 score_tag = item.find("span", class_="rating_nums") book_info["评分"] = score_tag.text.strip() if score_tag else "无评分" # 5. 简介 inq_tag = item.find("span", class_="inq") book_info["简介"] = inq_tag.text.strip() if inq_tag else "无简介" book_list.append(book_info) except Exception as e: print(f"提取单条图书数据失败:{e}") continue print(f"共提取到 {len(book_list)} 条图书数据") return book_list # ===================== 4. 数据清洗 ===================== def clean_data(book_list): """ 清洗数据,统一格式,去除冗余 :param book_list: 提取后的字典列表 :return: 清洗后的字典列表 """ cleaned_list = [] for book in book_list: cleaned_book = {} # 排名转为整数 cleaned_book["排名"] = int(book["排名"]) if book["排名"].isdigit() else 0 # 书名去除特殊字符 cleaned_book["书名"] = re.sub(r'[^\u4e00-\u9fa5a-zA-Z0-9\s]', '', book["书名"]) # 作者/出版社/出版时间去除首尾空格 cleaned_book["作者"] = book["作者"].strip() cleaned_book["出版社"] = book["出版社"].strip() cleaned_book["出版时间"] = book["出版时间"].strip() # 评分转为浮点数 cleaned_book["评分"] = float(book["评分"]) if book["评分"].replace(".", "").isdigit() else 0.0 # 简介去除换行符 cleaned_book["简介"] = book["简介"].replace("\n", "").strip() cleaned_list.append(cleaned_book) return cleaned_list # ===================== 5. 结果输出 ===================== def output_result(book_list): """ 输出结果:控制台结构化输出 + 保存为TXT文件 :param book_list: 清洗后的字典列表 """ if not book_list: print("无数据可输出") return # 1. 控制台输出 print("\n=== 豆瓣读书Top250(第一页) ===") for book in book_list: print(f"排名:{book['排名']} | 书名:{book['书名']}") print(f"作者:{book['作者']} | 出版社:{book['出版社']} | 出版时间:{book['出版时间']}") print(f"评分:{book['评分']} | 简介:{book['简介']}") print("-" * 80) # 2. 保存为TXT文件 with open("豆瓣读书Top250_第一页.txt", "w", encoding="utf-8") as f: f.write("豆瓣读书Top250(第一页)\n") f.write("=" * 80 + "\n") for book in book_list: f.write(f"排名:{book['排名']}\n") f.write(f"书名:{book['书名']}\n") f.write(f"作者:{book['作者']}\n") f.write(f"出版社:{book['出版社']}\n") f.write(f"出版时间:{book['出版时间']}\n") f.write(f"评分:{book['评分']}\n") f.write(f"简介:{book['简介']}\n") f.write("-" * 80 + "\n") print("\n数据已保存至:豆瓣读书Top250_第一页.txt") # ===================== 主函数:整合全流程 ===================== if __name__ == "__main__": # 1. 发送请求,获取HTML html = get_html(url, headers) # 2. 提取数据 raw_data = extract_data(html) # 3. 数据清洗 cleaned_data = clean_data(raw_data) # 4. 结果输出 output_result(cleaned_data)3.3 输出结果展示
控制台输出(节选)
plaintext
请求成功,响应状态码:200 共提取到 25 条图书数据 === 豆瓣读书Top250(第一页) === 排名:1 | 书名:红楼梦 作者:曹雪芹 高鹗 | 出版社:人民文学出版社 | 出版时间:1996年2月 评分:9.6 | 简介:满纸荒唐言,一把辛酸泪。都云作者痴,谁解其中味? -------------------------------------------------------------------------------- 排名:2 | 书名:活着 作者:余华 | 出版社:作家出版社 | 出版时间:2012年8月 评分:9.3 | 简介:人是为活着本身而活着的,而不是为活着之外的任何事物所活着。 -------------------------------------------------------------------------------- 排名:3 | 书名:百年孤独 作者:加西亚·马尔克斯 范晔 译 | 出版社:南海出版公司 | 出版时间:2011年6月 评分:9.3 | 简介:生命中真正重要的不是你遭遇了什么,而是你记住了哪些事,又是如何铭记的。 -------------------------------------------------------------------------------- ... 数据已保存至:豆瓣读书Top250_第一页.txtTXT 文件输出(节选)
plaintext
豆瓣读书Top250(第一页) ================================================================================ 排名:1 书名:红楼梦 作者:曹雪芹 高鹗 出版社:人民文学出版社 出版时间:1996年2月 评分:9.6 简介:满纸荒唐言,一把辛酸泪。都云作者痴,谁解其中味? -------------------------------------------------------------------------------- 排名:2 书名:活着 作者:余华 出版社:作家出版社 出版时间:2012年8月 评分:9.3 简介:人是为活着本身而活着的,而不是为活着之外的任何事物所活着。 --------------------------------------------------------------------------------四、核心环节深度解析
4.1 请求构造:模拟浏览器避免反爬
- 配置完整的请求头(
User-Agent、Referer、Accept),模拟 Chrome 浏览器请求,避开豆瓣的基础反爬机制; - 设置超时时间(
timeout=15),避免请求长时间阻塞; - 异常捕获覆盖 HTTP 错误、网络错误、未知错误,确保程序稳定性。
4.2 数据提取:BeautifulSoup vs 正则表达式
静态网页数据提取有两种核心方式,各有优劣:
| 提取方式 | 实现示例 | 优势 | 劣势 |
|---|---|---|---|
| BeautifulSoup(标签定位) | item.find("span", class_="rating_nums").text | 代码可读性高,适配 HTML 结构变化,无需写复杂正则 | 解析速度略慢,依赖标签 /class 属性 |
| 正则表达式(文本匹配) | re.findall(r'<span class="rating_nums">(.*?)</span>', html) | 解析速度快,可匹配无固定标签的数据 | 代码可读性差,HTML 结构变化易失效 |
实战建议:静态网页优先使用 BeautifulSoup,仅当标签结构极不稳定时使用正则表达式。
4.3 数据清洗:提升数据可用性
- 类型转换:将排名(字符串)转为整数,评分(字符串)转为浮点数,便于后续排序 / 计算;
- 字符过滤:使用正则表达式去除书名中的特殊字符,避免乱码 / 冗余;
- 空值兼容:对无简介、无评分的字段设置默认值,避免程序报错;
- 空格清理:去除字段首尾空格,统一数据格式。
4.4 结果输出:多格式适配需求
- 控制台输出:结构化展示数据,便于调试和快速查看;
- TXT 文件保存:持久化存储数据,便于后续查看和使用;
- 拓展方向:可通过
csv库保存为 CSV 文件,或通过openpyxl保存为 Excel 文件,适配数据分析场景。
五、常见问题与解决方案
| 问题现象 | 原因分析 | 解决方案 |
|---|---|---|
| 提取数据为空 | 1. HTML 解析器错误(如未安装 lxml);2. 标签 /class 属性拼写错误 | 1. 安装 lxml 解析器(pip install lxml);2. 核对浏览器中标签的 class 属性(注意空格 / 大小写) |
| 数据乱码 | 响应解码编码与网页实际编码不一致 | 1. 查看网页Content-Type响应头确认编码;2. 尝试gbk/gb2312等编码(如中文网页) |
| 部分数据提取失败 | 个别图书的 HTML 结构不一致(如无简介) | 在提取时增加异常捕获,为缺失字段设置默认值 |
| 保存文件时编码错误 | 未指定文件编码为 utf-8 | 打开文件时指定encoding="utf-8" |
六、拓展优化:爬取多页数据
静态网页爬取的进阶需求是多页数据爬取,以豆瓣读书 Top250 为例,其分页 URL 规律为:
- 第一页:
https://book.douban.com/top250 - 第二页:
https://book.douban.com/top250?start=25 - 第三页:
https://book.douban.com/top250?start=50 - 分页规则:
start参数为(页码 - 1)*25
多页爬取核心代码(整合到主函数)
python
运行
# 多页爬取:爬取前3页 if __name__ == "__main__": all_book_data = [] # 遍历前3页 for page in range(3): start = page * 25 # 构造分页URL page_url = f"https://book.douban.com/top250?start={start}" if start > 0 else "https://book.douban.com/top250" print(f"\n===== 爬取第 {page+1} 页:{page_url} =====") # 1. 获取HTML html = get_html(page_url, headers) # 2. 提取数据 raw_data = extract_data(html) # 3. 数据清洗 cleaned_data = clean_data(raw_data) # 4. 合并数据 all_book_data.extend(cleaned_data) # 增加请求间隔,避免反爬 import time time.sleep(1) # 输出所有数据 output_result(all_book_data)七、总结
静态网页爬取是爬虫开发的基础,其核心流程 “请求 - 解析 - 提取 - 清洗 - 输出” 适用于所有爬虫场景。本文以豆瓣读书 Top250 为实战案例,完整演示了静态网页爬取的全流程,重点讲解了请求构造的反爬技巧、BeautifulSoup 的数据提取方法、数据清洗的标准化操作以及结果输出的多格式适配。
在实际开发中,需注意以下核心要点:
- 模拟浏览器请求,避免被基础反爬机制拦截;
- 优先使用结构化解析工具(如 BeautifulSoup),提升代码可维护性;
- 数据清洗是提升数据可用性的关键,需处理空值、格式、类型等问题;
- 多页爬取时需遵循网站分页规则,增加请求间隔,避免请求频率过高。
掌握静态网页爬取的全流程,能帮助开发者建立标准化的爬虫开发思维,为后续处理动态网页(如 AJAX 加载、JavaScript 渲染)、复杂反爬网页奠定坚实基础。