☰
Python爬虫入门:requests+BeautifulSoup抓取h1标题实战
2026/11/2 8:27:17 网站建设 项目流程

简介:本资源是一份面向Python初学者的网络爬虫入门实践指南,聚焦requests与BeautifulSoup两大核心库的协同使用,解决网页标题(

标签)自动化提取这一典型Web Scraping任务。内容涵盖HTTP请求发送、HTML响应解析、CSS选择器基础应用及异常处理要点,兼顾技术规范性与实操友好性,适合零基础或具备基本Python语法能力的学习者快速上手。资源为单文件docx文档,共1个文件,大小仅14KB,结构清晰,含完整可运行代码、分步注释、环境配置命令(pip install)及关键注意事项说明,便于边学边练。目前已有133人学习下载,读者可直接复用示例代码,掌握从请求发起、响应校验、DOM遍历到文本提取的全流程开发逻辑,并建立对robots.txt合规性、反爬策略等工程实践的认知基础。

1. 为什么一个“只抓

”的爬虫,反而最适合作为毕设/课设第一版交付?

很多学生第一次做爬虫类毕设或课设时,一上来就想抓新闻正文、商品价格、用户评论——结果卡在反爬验证、JavaScript渲染、登录态维持上,两周没跑出一行有效数据,最后答辩前通宵改PPT硬凑“系统架构图”。而这个看似简单的“requests + BeautifulSoup 抓

”实例,恰恰是我在带三届课程设计时反复验证过的最小可交付闭环:它不依赖前端渲染、不涉及会话管理、不触发风控机制,但完整覆盖了网络请求→响应解析→结构化提取→异常处理这四个核心链路。更重要的是,它天然支持“渐进式扩展”——今天能跑通example.com的

,明天就能换成学校官网的新闻标题列表,后天加个循环遍历分页URL,再往后接入数据库存档、加日志监控、套个Flask接口……整条技术演进路径清晰可见。适合零爬虫经验但有Python基础的同学,用3小时搭出可演示、可截图、可写进报告“系统功能模块”的真实运行效果。


2. 技术选型与环境准备:为什么不是Selenium、Scrapy或PyQuery?

2.1 requests + BeautifulSoup 组合的不可替代性

在教学场景和轻量级数据采集任务中,requests 和 BeautifulSoup 的组合不是“将就”,而是经过权衡后的最优解。requests 负责干净利落地完成 HTTP 协议层交互:它不启动浏览器、不加载CSS/JS资源、不维护渲染上下文,因此内存占用低、启动快、失败原因明确(如 status_code=403 就是被拒,503 就是服务端忙)。BeautifulSoup 则专注 HTML/XML 文档树的解析与查询,其find_all()和select()方法对初学者极其友好——不需要理解 DOM 树遍历算法,也不用写 XPath 表达式,直接用类似 CSS 选择器的语法就能定位元素。对比来看:Selenium 启动 ChromeDriver 需要额外安装、配置、等待页面加载,调试时控制台输出冗长;Scrapy 框架结构重,需定义 Spider 类、Item 类、Pipeline 类,学习曲线陡峭;PyQuery 虽语法简洁,但底层仍依赖 lxml,错误提示不如 BeautifulSoup 直观。而本方案中,soup.find_all('h1')这一行代码背后,已隐含了编码自动检测(如 UTF-8 / GBK)、标签容错修复(自动闭合未闭合标签)、树结构重建等能力——这些“看不见的功夫”,正是新手能快速获得正向反馈的关键。

2.2 环境搭建实操:避开 pip 源与编码陷阱

# 推荐使用国内镜像源加速安装(避免超时) pip install -i https://pypi.tuna.tsinghua.edu.cn/simple/ requests beautifulsoup4 # 验证安装是否成功(注意:不要跳过这步!) python -c "import requests, bs4; print('✅ requests version:', requests.__version__); print('✅ bs4 version:', bs4.__version__)"

提示:若执行报错ModuleNotFoundError: No module named 'bs4',大概率是安装时终端未激活正确 Python 环境(如用了 conda 但 pip 指向系统 Python),请统一使用python -m pip install ...命令。Windows 用户若遇到中文乱码,需在脚本开头添加# -*- coding: utf-8 -*-并确保编辑器保存为 UTF-8 编码。

2.3 requests 的关键参数设计逻辑

import requests # 实际项目中绝不能裸用 requests.get(url),必须显式控制以下参数: response = requests.get( url='http://example.com', timeout=(3, 7), # 元组形式:(连接超时, 读取超时),避免卡死 headers={ # 模拟真实浏览器请求头,绕过基础反爬 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' }, allow_redirects=True, # 允许重定向(如 http → https),否则可能拿不到最终页面 verify=True # 生产环境务必保持 True,禁用 SSL 验证仅用于测试自签名证书 )
  • timeout参数是血泪经验:不设超时,当目标服务器无响应时,脚本会无限等待,导致整个程序假死;
  • headers中的User-Agent是入门级反爬的第一道门槛,多数网站会拒绝python-requests/2.x这类默认标识;
  • allow_redirects=True看似默认值,但某些旧版 requests 默认为 False,显式声明可避免跨环境差异。

3. 核心模块实现:从单页

提取到多页标题聚合

3.1 单页标题提取:不只是find_all('h1'),还要处理空值与嵌套

from bs4 import BeautifulSoup import requests def extract_h1_titles(url): try: response = requests.get(url, timeout=(3, 7), headers={ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' }) response.raise_for_status() # 自动抛出 4xx/5xx 异常,比 if status_code == 200 更健壮 soup = BeautifulSoup(response.content, 'html.parser') # 用 content 而非 text,避免编码二次解析错误 # 方案一:传统 find_all,兼容老版本 BS4 h1_list = [] for tag in soup.find_all('h1'): text = tag.get_text(strip=True) # strip=True 自动去除首尾空白和换行 if text: # 过滤空标题(如 <h1></h1> 或仅含空格) h1_list.append(text) # 方案二:CSS 选择器(更灵活,支持 class/id 属性过滤) # h1_list = [tag.get_text(strip=True) for tag in soup.select('h1.title, h1.main-heading')] return h1_list except requests.exceptions.Timeout: print(f"❌ 请求超时:{url}") return [] except requests.exceptions.ConnectionError: print(f"❌ 连接失败:{url}") return [] except requests.exceptions.HTTPError as e: print(f"❌ HTTP 错误:{url} -> {e}") return [] except Exception as e: print(f"❌ 未知错误:{url} -> {e}") return [] # 测试调用 titles = extract_h1_titles('http://example.com') print(f"共提取 {len(titles)} 个标题:") for i, t in enumerate(titles, 1): print(f" {i}. {t}")
  • 关键点说明:response.content比response.text更可靠,因为后者由 requests 自动解码,可能因网页<meta charset>声明不规范导致乱码;而content是原始字节流,交由 BeautifulSoup 按其内置规则(如查看<meta>标签)自动识别编码。
  • get_text(strip=True)是防坑重点:HTML 中<h1>\n 新闻头条 \n</h1>若不 strip,会得到带换行和空格的字符串,后续存入数据库或导出 CSV 时易引发格式错乱。
  • response.raise_for_status()替代手动判断status_code,代码更简洁且自动覆盖所有错误状态码(如 401、403、429、500 等)。

3.2 多页批量抓取:URL 列表驱动与结果归并

def batch_extract_titles(url_list, output_file='titles.txt'): all_titles = [] for i, url in enumerate(url_list, 1): print(f"[{i}/{len(url_list)}] 正在处理:{url}") titles = extract_h1_titles(url) if titles: all_titles.extend([f"[{url}] {t}" for t in titles]) else: print(f" ⚠️ 该页面未提取到有效标题") # 写入文件(UTF-8 编码,确保中文不乱码) with open(output_file, 'w', encoding='utf-8') as f: f.write("=== 批量抓取标题汇总 ===\n\n") for line in all_titles: f.write(line + "\n") print(f"\n✅ 全部完成!共提取 {len(all_titles)} 条标题,已保存至 {output_file}") # 示例 URL 列表(实际使用时替换为真实目标) urls = [ 'http://example.com', 'https://httpbin.org/html', # 测试用公开 HTML 页面 'https://httpbin.org/response-headers?Content-Type=text/html;charset=utf-8' ] batch_extract_titles(urls)
  • 设计逻辑:采用“URL 列表驱动”而非硬编码循环,便于后期扩展为从 Excel/CSV 文件读取 URL,或对接数据库查询生成 URL。
  • 输出格式强调可读性:每条标题前缀[URL],方便回溯来源;文件头部加时间戳和分隔线,符合工程文档习惯。
  • 错误容忍:单个 URL 失败不影响整体流程,仅打印警告并继续下一个,避免“一损俱损”。

3.3 结构化存储:从文本文件到 JSON 格式化输出

import json from datetime import datetime def save_as_json(url_list, output_file='titles.json'): results = { "meta": { "generated_at": datetime.now().isoformat(), "total_urls": len(url_list), "total_titles": 0 }, "data": [] } for url in url_list: titles = extract_h1_titles(url) results["data"].append({ "url": url, "h1_titles": titles, "count": len(titles) }) results["meta"]["total_titles"] += len(titles) # 写入 JSON(ensure_ascii=False 保证中文正常显示) with open(output_file, 'w', encoding='utf-8') as f: json.dump(results, f, ensure_ascii=False, indent=2) print(f"✅ JSON 格式结果已保存:{output_file}") return results # 调用示例 json_result = save_as_json([ 'http://example.com', 'https://httpbin.org/html' ])
  • ensure_ascii=False是关键:默认json.dump会把中文转成\u4f60\u597d这类 Unicode 转义,开启后直接输出可读中文。
  • indent=2提升 JSON 可读性,方便人工检查和调试;生产环境可改为indent=None减小体积。
  • 元数据(meta字段)包含生成时间、统计总数,这是交付物专业性的体现——导师或评审一眼就能看到“这不是随手跑的,是有元信息管理的”。

4. 避坑指南:新手最容易翻车的5个致命细节

4.1 现象:AttributeError: 'NoneType' object has no attribute 'get_text'

原因:soup.find()返回None(未找到匹配标签),却直接调用.get_text()。常见于误用find()代替find_all(),或 CSS 选择器写错。
解决:永远对find()结果判空,或改用find_all()+ 循环;若必须用find(),加保护:

tag = soup.find('h1') if tag: title = tag.get_text(strip=True) else: title = "未找到标题"

4.2 现象:中文乱码,标题显示为??????或新闻头æ¡

原因:requests 自动解码失败(如网页声明charset=gb2312但 requests 误判为 ISO-8859-1),或 BeautifulSoup 解析时未指定编码。
解决:强制指定解析器编码:

soup = BeautifulSoup(response.content, 'html.parser', from_encoding='utf-8') # 或更稳妥:先获取响应编码,再传入 soup = BeautifulSoup(response.content, 'html.parser', from_encoding=response.encoding or 'utf-8')

4.3 现象:requests.exceptions.SSLError: HTTPSConnectionPool

原因:目标网站使用自签名证书或证书链不全,requests 默认校验 SSL 失败。
解决:仅限测试环境临时禁用验证(生产环境必须修复证书):

response = requests.get(url, verify=False) # ⚠️ 加上这行会警告,可忽略 import urllib3 urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) # 抑制警告

4.4 现象:爬取结果为空,但浏览器能正常打开网页

原因:目标网页标题不在<h1>标签内,而是在<title>标签(网页标题栏文字),或使用了<h2>/<h3>作为主标题。
解决:先用浏览器开发者工具(F12 → Elements)确认真实标签结构;若需抓<title>,用soup.title.get_text();若需抓所有标题层级,用soup.select('h1, h2, h3')。

4.5 现象:频繁被封 IP,返回 403 或 429 状态码

原因:未设置请求间隔,高频请求触发服务器反爬机制。
解决:在循环中加入随机延时(避免固定间隔被识别为脚本):

import time import random for url in url_list: # ... 抓取逻辑 ... time.sleep(random.uniform(1, 3)) # 每次请求后随机等待 1~3 秒

5. 总体架构设计:三层结构与四个模块的职责划分

5.1 架构图描述(文字版)

本方案采用清晰的三层结构:

  • 数据获取层:负责网络通信,封装requests.get()调用,处理超时、重试、请求头、SSL 配置;
  • 数据解析层:负责 HTML 解析,封装 BeautifulSoup 初始化与选择器查询,输出标准化文本列表;
  • 数据输出层:负责结果持久化,支持文本、JSON 两种格式,记录元数据与来源追溯信息。

四个核心模块对应具体职责:

  1. URL 管理模块:维护待爬 URL 列表,支持从硬编码、文件、命令行参数三种方式加载;
  2. HTTP 客户端模块:封装 requests 调用,提供fetch_page()方法,统一处理异常与日志;
  3. HTML 解析模块:提供parse_h1()等方法,屏蔽 BeautifulSoup 底层细节,暴露语义化接口;
  4. 结果导出模块:提供save_to_txt()和save_to_json()方法,确保输出格式规范、编码正确、元信息完整。

5.2 模块间依赖关系与松耦合设计

各模块通过函数参数传递数据,不共享全局变量,也不互相 import 内部实现细节。例如:

  • HTTP 客户端模块只返回response对象或None,不关心后续如何解析;
  • HTML 解析模块接收response.content字节流,不关心 URL 来源;
  • 结果导出模块接收纯 Python 列表(如['标题1', '标题2']),不依赖 requests 或 BeautifulSoup。

这种设计带来两大好处:

  • 可测试性强:每个模块可独立单元测试,如用unittest.mock模拟requests.get返回固定 HTML 字符串,验证解析逻辑;
  • 可替换性高:未来若需升级为 Selenium,只需重写HTTP 客户端模块,其余模块完全不动。

5.3 数据表设计(适用于需存入数据库的扩展场景)

若后续需将标题存入 SQLite,建议建如下表结构(符合第三范式,预留扩展性):

字段名类型说明
idINTEGER PRIMARY KEY AUTOINCREMENT主键,自增
url_idINTEGER NOT NULL外键,关联 urls 表
title_textTEXT NOT NULL提取的标题文本(已 strip)
created_atDATETIME DEFAULT CURRENT_TIMESTAMP插入时间
source_tagTEXT DEFAULT 'h1'标签名(支持 h1/h2/h3 扩展)

配套urls表(存储去重后的目标 URL):

字段名类型说明
idINTEGER PRIMARY KEY AUTOINCREMENT主键
urlTEXT UNIQUE NOT NULL完整 URL
last_crawledDATETIME最后抓取时间
statusTEXT DEFAULT 'active'状态(active/inactive/error)

提示:SQLite 无需单独安装驱动,Python 内置sqlite3模块即可操作。建表 SQL 示例:

CREATE TABLE IF NOT EXISTS urls ( id INTEGER PRIMARY KEY AUTOINCREMENT, url TEXT UNIQUE NOT NULL, last_crawled DATETIME, status TEXT DEFAULT 'active' );

6. 验收标准与答辩技巧:如何让导师一眼认可你的工作量

6.1 可量化的验收清单(答辩前逐项自查)

验收项达标标准检查方式是否完成
✅ 环境可复现在另一台干净电脑上,仅执行pip install+ 运行脚本,10 分钟内出结果新建虚拟环境测试□
✅ 单页抓取稳定对example.com和httpbin.org/html均能正确提取<h1>文本,无乱码、无空值手动运行并截图输出□
✅ 多页批量处理输入 3 个不同 URL,输出文件中每条标题带[URL]前缀,来源可追溯检查输出文件内容□
✅ 异常全面覆盖故意输入无效 URL(如http://invalid-domain-123.com),程序不崩溃,仅打印错误提示修改 URL 后运行观察□
✅ 输出格式规范titles.json文件可被 VS Code 正常打开,中文显示正常,有meta和data结构用文本编辑器打开验证□
✅ 代码结构清晰源码按fetch.py(HTTP)、parse.py(解析)、export.py(导出)分文件,无大杂烩查看项目目录结构□

这张表不是摆设——我带的学生里,有 A 同学答辩时直接打开 PPT 的这一页,对照着一条条勾选,导师边听边点头:“嗯,这个结构意识很好”,当场给了高分。因为表格把“做了什么”转化成了“怎么证明做到了”,比空谈“我实现了XX功能”有力十倍。

6.2 答辩话术设计:用“问题驱动”替代“功能罗列”

不要说:“我用了 requests 和 BeautifulSoup,实现了标题提取。”
而要说:“我们最初想快速验证数据可获取性,发现直接访问网页能拿到标题,但手动复制效率太低。于是拆解问题:第一步,如何自动化发请求?选 requests 因为它轻量、可控、错误明确;第二步,如何从 HTML 里捞出标题?BeautifulSoup 的select('h1')语法最接近前端开发直觉,学半小时就能上手;第三步,如何让结果能复用?我们设计了 JSON 输出,带时间戳和来源标记,下次想加‘发布时间’字段,只要在解析层多提一个<time>标签就行——整个架构是为后续扩展留了活口的。”

这种表达,把技术选型变成了“解题思路”,把代码实现变成了“问题演化过程”,导师立刻能感知你的工程思维深度。

6.3 三个必答问题预演与应答策略

Q1:为什么不用 Scrapy?它不是更专业吗?
→ 回应重点:场景匹配度。“Scrapy 确实强大,但它面向大规模、长期运行的分布式爬虫。而本次任务是单次、小批量、教学验证性质——就像学开车先练倒库,没必要一上来就开赛车。requests+BS4 让我能 100% 理解每一行代码在做什么,出了错知道去哪改。等我把这套逻辑吃透,再迁移到 Scrapy,就是水到渠成。”

Q2:如果目标网站没有<h1>,只有<h2>,你怎么改?
→ 展示可维护性。“只需要改解析层的一行代码:把soup.find_all('h1')换成soup.find_all(['h1','h2']),或者用 CSS 选择器soup.select('h1, h2')。所有其他模块——请求、存储、日志——完全不用动。这也印证了我们分层设计的价值:变化只发生在解析层,影响范围可控。”

Q3:你提到遵守 robots.txt,具体怎么做的?
→ 体现工程素养。“目前脚本未主动读取 robots.txt,但我们在文档里明确写了‘使用前请自查目标站 robots.txt’,并在代码注释中标注了合规提醒。下一步计划增加robotparser模块,在发起请求前自动检查disallow规则——这属于可迭代的合规增强,不是当前 MVP 的阻塞项。”

从那以后我每次带学生做爬虫类课题,都强制他们先用这个<h1>实例跑通全流程,再谈扩展。因为真正的工程能力,不在于堆砌多少酷炫技术,而在于能否用最简路径,把一件事从需求、实现到交付,闭环走通。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询