Python爬虫基础代码模板:Requests请求与BeautifulSoup解析示例
本文提供一套规范实用的Python爬虫基础代码模板,系统讲解如何使用Requests库发起稳定HTTP请求,结合BeautifulSoup高效定位与解析DOM节点,并给出异常处理、编码校验与防踩坑实践指南。
假设你正准备从一个结构规整的资讯网站或技术博客中批量抽取文章标题、发布时间和详情链接。如果在脚本中仅仅写出 requests.get(url).text,往往很快就会碰到棘手的问题:程序因为某一次请求卡住数分钟、中文字符全显示为乱码、触发目标站点防御返回 403 状态码,或者因为某个网页缺少指定标签而直接报出 AttributeError: 'NoneType' object has no attribute 'text' 并中断退出。构建稳定爬虫的关键,不仅在于发出网络请求,更在于建立包含请求头伪装、超时熔断、状态校验、安全提取与异常兜底的规范流程。本文将围绕 Requests 与 BeautifulSoup 提供一套开箱即用的通用爬虫工程模板,深入拆解核心步骤与常见边界问题。
一、爬虫执行流程与适用边界
在编写代码前,必须明确 Requests 与 BeautifulSoup 组合的技术边界与职责分工。Requests 负责底层的 HTTP/HTTPS 网络传输,通过模拟客户端行为获取服务器下发的原始响应;BeautifulSoup 则作为 HTML/XML 语法解析器,将返回的纯文本字符串解析为树状 DOM 结构,提供结构检索与属性提取接口。
这一经典技术方案极其适合处理服务器直接渲染输出的静态网页(例如传统博客、门户资讯、文档手册等)。如果目标站点的主要内容高度依赖前端 JavaScript 在浏览器本地动态异步生成(例如单页 SPA 应用),则应当优先通过浏览器开发者工具网络面板直接调用其后端 JSON API,或转向 Playwright、Selenium 等自动化渲染工具。
二、依赖安装与运行环境准备
完成本次爬虫任务需要安装 requests 发起请求,安装 beautifulsoup4 用于解析 DOM。同时强烈建议安装 lxml 模块,作为 BeautifulSoup 的底层解析引擎。相比 Python 自带的 html.parser,lxml 是基于 C 语言编写的高性能解析库,在解析速度与对残缺破损 HTML 的容错能力上均有明显优势。
# 推荐在隔离的虚拟环境中执行依赖安装
pip install requests beautifulsoup4 lxml
执行完成后,可通过在 Python 终端中导入检查是否安装成功:
# 验证依赖可用性示例
python -c "import requests, bs4, lxml; print('环境准备就绪')"
三、第一步:构建健壮的 Requests 请求机制
使用 Requests 发起 HTTP 请求时,绝不能仅传入一个简单的 URL。很多网站会直接拦截默认标头,网络波动也可能导致无响应挂起。在编写请求逻辑时,必须统一实施以下四项防护规则:
- 设置标准请求头(Headers):大多数服务器通过
User-Agent识别客户端。若不显式指定,Requests 会附带python-requests/x.y.z的默认标识,极易被直接返回 403 Forbidden。 - 设定显式超时限制(Timeout):如果服务器处理缓慢或网络丢包,缺少
timeout参数的请求会导致程序无限期等待。建议传入二元组timeout=(5, 15),分别表示连接超时 5 秒与读取超时 15 秒。 - 状态码校验(raise_for_status):请求完成后,必须通过
response.raise_for_status()进行断言,当返回 4xx 客户端错误或 5xx 服务端错误时立即抛出HTTPError,避免向后续解析层传递错误的 HTML 错误页。 - 编码检测与修复:部分中文网站在响应头中缺少明确字符集标识,Requests 会默认以 ISO-8859-1 解码导致生僻字或全角符号显示为乱码。使用
response.apparent_encoding能够基于内容字节流推断真实编码(如 UTF-8 或 GB18030)。
四、第二步:精通 BeautifulSoup 节点检索与属性提取
完成 HTML 下载后,BeautifulSoup 会将扁平的文本转换为包含根节点、标签(Tag)、属性(NavigableString / Dict)的内存树。在实际检索节点时,主要有两种路径:标签方法族(find / find_all)与 CSS 选择器(select / select_one)。
| 检索机制 | 常用方法 | 语法特征与传参 | 最佳适用场景 |
|---|---|---|---|
| 标签方法族 | find() / find_all() |
传入标签名及属性映射,如 soup.find('div', class_='article-card') |
层级明确、属性专一的独立容器查找,代码语义直观。 |
| CSS 选择器 | select_one() / select() |
传入 CSS 选择器字符串,如 soup.select('div.content > ul > li a.title') |
多层嵌套、父子组合路径或直接复用浏览器开发者工具中的选择器。 |
五、第三步:通用爬虫基础代码模板
以下代码提供了一个结构完备、具备异常处理与数据规整能力的通用爬虫脚本。该模板将网络拉取(fetch_page)与数据解析(parse_articles)严格解耦,并包含主执行流程与结构化输出示例:
import logging
import sys
from urllib.parse import urljoin
from typing import List, Dict, Optional
import requests
from bs4 import BeautifulSoup
from requests.exceptions import RequestException
# 配置结构化日志输出
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(message)s",
handlers=[logging.StreamHandler(sys.stdout)]
)
# 基础常量与通用浏览器标头
DEFAULT_HEADERS = {
"User-Agent": (
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/124.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",
}
def fetch_page(url: str, timeout: tuple = (5, 15)) -> Optional[str]:
"""
负责安全地向指定 URL 发起 HTTP GET 请求并返回页面文本。
:param url: 目标页面地址
:param timeout: 超时限制元组 (连接超时, 读取超时)
:return: 成功返回 HTML 文本,发生网络异常或非 200 响应时返回 None
"""
try:
response = requests.get(url, headers=DEFAULT_HEADERS, timeout=timeout)
# 校验 HTTP 状态码,若为 4xx 或 5xx 会主动抛出 HTTPError
response.raise_for_status()
# 针对中文编码进行智能推断,防止部分站点产生乱码
if response.encoding.lower() in ("iso-8859-1", "ascii"):
response.encoding = response.apparent_encoding
return response.text
except RequestException as exc:
logging.error("请求失败: %s | 原因: %s", url, exc)
return None
def parse_articles(html_content: str, base_url: str) -> List[Dict[str, str]]:
"""
使用 BeautifulSoup 解析文章列表 HTML 并提取结构化字段。
:param html_content: 原始 HTML 文本
:param base_url: 基础地址,用于将相对路径转换为绝对路径
:return: 结构化文章字典列表
"""
results: List[Dict[str, str]] = []
# 使用容错力与性能俱佳的 lxml 解析器
soup = BeautifulSoup(html_content, "lxml")
# 模拟定位:查找所有包含文章卡片的 DOM 容器
article_nodes = soup.select("article.post-item, div.article-card, div.list-item")
# 若选择器未命中,提供回退选择器(如基础 li 容器)
if not article_nodes:
article_nodes = soup.find_all("li", class_="item")
for node in article_nodes:
# 1. 安全提取标题及超链接
title_tag = node.select_one("h2 a, .title a, a.article-title")
if not title_tag:
continue
title_text = title_tag.get_text(strip=True)
raw_href = title_tag.get("href", "").strip()
absolute_url = urljoin(base_url, raw_href) if raw_href else ""
# 2. 提取摘要信息(若不存在则置空,不引发异常)
summary_tag = node.select_one("p.summary, .desc, p.excerpt")
summary_text = summary_tag.get_text(strip=True) if summary_tag else ""
# 3. 提取发布时间
date_tag = node.select_one("time, span.date, .publish-time")
date_text = date_tag.get_text(strip=True) if date_tag else ""
# 收集有效条目
results.append({
"title": title_text,
"url": absolute_url,
"summary": summary_text,
"date": date_text
})
return results
def run_spider(target_url: str):
"""
爬虫任务主入口调度函数
"""
logging.info("开始抓取目标页面: %s", target_url)
html_text = fetch_page(target_url)
if not html_text:
logging.warning("未能获取页面内容,爬取流程终止。")
return []
records = parse_articles(html_text, base_url=target_url)
logging.info("页面解析完毕,成功提取到 %d 条数据记录。", len(records))
# 打印前三条样例结果
for idx, item in enumerate(records[:3], start=1):
logging.info("示例记录 [%d] %s -> %s (%s)", idx, item["title"], item["url"], item["date"])
return records
if __name__ == "__main__":
# 实际测试时请替换为合法的目标站点 URL
EXAMPLE_URL = "https://example.com/blog"
run_spider(EXAMPLE_URL)
六、关键陷阱与排查清单
初学者在运行爬虫时,经常会遇到以下四种典型故障。请结合对应现象核对解决思路:
1. 触发 HTTP 403 Forbidden 拦截
原因排查:目标站点通过网关或 WAF 检查客户端标头。若缺少真实 User-Agent、或者缺少 Referer、Accept 等字段,会直接拦截请求。
解决办法:打开浏览器 F12 开发者工具,在 Network(网络)面板中找到对应的文档请求,右键复制其完整的 Request Headers,将对应键值写入 Python 脚本的字典中。
2. 中文内容显示为乱码(例如 æµ 等)
原因排查:HTTP 响应头中的 Content-Type 未标明 charset=utf-8,导致 Requests 采用默认的 Latin-1(ISO-8859-1)解码。
解决办法:在访问 response.text 前加入代码 response.encoding = response.apparent_encoding,或者直接使用 response.content.decode('utf-8', errors='replace') 显式指定正确的编码。
3. 提取属性时报 AttributeError: 'NoneType' object
原因排查:所编写的 CSS 选择器或 find() 未能在该节点中匹配到目标元素,返回了 None,紧接着直接调用 .text 或 ['href'] 引发运行时异常。
解决办法:务必先做非空检查,或者在提取方法中使用 node.get('attr') 替代方括号索引;针对文本使用三元表达式:tag.get_text(strip=True) if tag else ""。
4. 页面在浏览器中可见,但爬虫返回的 HTML 中没有对应内容
原因排查:该数据不是服务器初始返回的 HTML 静态内容,而是浏览器加载页面后通过 Ajax/Fetch 请求异步拉取的,或者是前端 Vue/React 组件在客户端渲染填充的。
解决办法:在开发者工具的 Network 面板中切换到 Fetch/XHR 标签页刷新页面,找到返回真实数据的后端接口,直接针对该 API 发送请求解析 JSON 格式数据。
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









