资讯详情

Python爬虫入门实战:Requests与BeautifulSoup从零抓取网页

📅 2026/10/12 5:00:41 | 华诺云谱 👁 阅读
Python爬虫入门实战:Requests与BeautifulSoup从零抓取网页
聊爬虫之前先说我自己的入门路径。当时就是从一行requests.get(url)开始的拿到网页文本再扔给 BeautifulSoup所有教程都这么写我也以为不过是两个库的事。直到被网站返回 429、被各种编码问题搞得晕头转向才意识到入门爬虫的关键根本不是工具而是对 HTTP 的理解和对页面结构的耐心。这个组合到今天依然是 Python Web 爬虫最经典的起点代码量小、反馈直接特别适合刚接触爬虫、想快速验证思路的朋友。它能帮你做的事情初期归纳起来就三件把目标网页完整抓下来、按层级把需要的字段抠出来、存成你方便用的格式。你不需要把 HTML 和 CSS 学到底只需要会看标签结构就能写出能用的采集脚本。下面这套流程我按自己实际操作时踩过的坑重新梳理过照着做基本能跑通。1. 环境准备与依赖安装1.1 Python 版本与虚拟环境正式动手前先把环境理清楚。建议直接使用 Python 3.8 以上版本太老的版本对类型注解、f-string这些语法支持不完整部分第三方库的新接口也会拒绝安装。如果你机器上同时存在多个 Python 版本命令行里使用python3和pip3来区分会更稳妥避免和系统自带的 Python 打架。虚拟环境是我反复强调的一步尤其是你未来要同时玩爬虫、数据分析、Web 开发这几个方向时。每个项目依赖的版本可能互相冲突今天爬虫需要requests 2.x明天另一个项目可能需要老版本接口全部装到全局环境里迟早出问题。用 venv 隔离是最轻量的方案mkdir crawler-demo cd crawler-demo python3 -m venv venv source venv/bin/activateWindows 上激活命令不一样是venv\Scripts\activate。激活成功后命令行前面会出现(venv)标识这时候再执行 pip 安装所有包都会装进当前虚拟环境不会污染全局。1.2 安装 Requests 和 BeautifulSoup激活虚拟环境后安装两个核心库pip install requests beautifulsoup4我特别标一下beautifulsoup4要带数字 4老教程里写的BeautifulSoup库是多年前的 3.x 版本早就停止维护了。安装时如果你看到Defaulting to user installation because normal site-packages is not writeable这种提示通常就是没激活虚拟环境、直接以系统 Python 执行 pip 导致的。最干净的处理方式不是加--user参数而是回到上一步把虚拟环境激活后再装。另外建议顺手把lxml也装上。BeautifulSoup 默认使用 Python 标准库里的html.parser对大多数页面够用但遇到结构比较复杂的 HTML解析速度和容错性都不如lxml。安装命令pip install lxml装完可以验证一下pip show requests pip show beautifulsoup4能看到版本信息就说明环境没问题。1.3 开发工具建议编辑器这块VSCode 是目前最省事的方案。打开项目文件夹后先通过命令面板执行Python: Select Interpreter选到刚才创建的虚拟环境。这样运行代码时才会正确加载 venv 里的包避免出现“终端里能 import编辑器里报 ModuleNotFoundError”的怪问题。写爬虫脚本时我建议开两个面板左边是代码右边是终端。因为爬虫调试本质上是一个“请求—解析—报错—修正”的循环频繁切换窗口会打断思路。代码写得再漂亮都不如多跑几次、多看几次真实页面来得实在。2. 核心原理解析Requests 与 BeautifulSoup 的协作方式2.1 HTTP 请求与响应爬虫的底层模型爬虫的底层逻辑其实就是模拟你打开浏览器的过程。你在地址栏输入 URL、按下回车浏览器会向目标服务器发送一个 HTTP 请求服务器返回 HTML、CSS、JavaScript 等资源浏览器再渲染成你看到的页面。requests.get(url)做的事情就是发送这个请求。它会返回一个Response对象里面装着服务器给的所有信息状态码、响应头、响应体。HTML 正文就在响应体里也就是resp.text。这个模型理解透了很多问题就能想明白。比如为什么有些网站会返回 403因为服务器检查了请求头里的 User-Agent发现你不是一个常规浏览器。为什么明明能看到页面内容resp.text却是一堆乱码因为响应体经过了压缩或非 UTF-8 编码需要额外处理。所有这些异常本质上都发生在 HTTP 层而不是解析层。2.2 Requests 的核心能力与常用方法Requests 最常用的方法就是get和post。入门阶段 90% 的场景用get就够了遇到搜索、登录、翻页提交表单时才需要post。先记住一个基础用法import requests url https://example.com/books headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0 Safari/537.36 } resp requests.get(url, headersheaders, timeout10) print(resp.status_code) print(resp.encoding)这里有两个参数必须养成习惯。第一是headers至少带上 User-Agent。很多网站的反爬第一道防线就是检查这个字段空着 UA 去访问大概率被拦。第二是timeout单位是秒。不设 timeout 的话遇到网络抖动、服务器无响应脚本会在那里挂很久看起来像死循环。响应对象上最常见的几个属性整理如下属性作用典型场景resp.status_codeHTTP 状态码判断请求是否成功200 正常404 不存在403 被拒绝resp.text解码后的文本内容传给 BeautifulSoup 解析resp.content原始字节内容下载图片、二进制文件时使用resp.headers响应头字典查看服务器返回的Retry-After、Content-Type等字段resp.url最终请求的 URL确认是否有重定向2.3 BeautifulSoup 的解析逻辑拿到resp.text之后就到了 BeautifulSoup 的表演时间。这个库做的事情可以理解为一句话把一串 HTML 字符串变成一棵可以查询的节点树。from bs4 import BeautifulSoup soup BeautifulSoup(resp.text, lxml)第二个参数是解析器。lxml比默认的html.parser容错性更强很多网页作者并不按规范写 HTML标签不闭合、嵌套混乱是家常便饭。lxml 在这种脏页面上更能扛。如果你没装 lxml也可以用html.parser顶替只是遇到复杂页面时解析速度会慢一些。解析完成后查数据有三种常用方式。第一种是soup.find()只返回第一个匹配节点第二种是soup.find_all()返回所有匹配节点的列表第三种是soup.select()接受 CSS 选择器表达更精炼。我个人的习惯是定位一条数据用find遍历一组数据用select。比如页面里所有书名都在h3标签下用soup.select(h3)一步到位比find_all写起来干净。取文本和取属性也有讲究。get_text()会拿标签内所有文字加stripTrue可以把首尾空白去掉。取链接、图片地址这类属性需要用tag[href]或tag[src]。注意直接取不存在的属性会抛异常可以用tag.get(href)安全地拿。3. 一个可复现的入门实例抓取一个静态页面3.1 明确采集目标与页面结构为了演示我构造一个纯静态的书籍列表页场景页面里有一个div.book-list容器里面每个div.book-item代表一本书其中包含书名h3.book-title、价格span.price、详情链接a标签的href属性。这是爬虫入门最典型的小目标提取列表页上所有书的名称、价格和链接。动手写代码前先打开浏览器按 F12 调出开发者工具找到你要抓的元素确认它的标签层级。这一步做不好后面写选择器就等于盲人摸象。你要的不是把这行 HTML 背下来而是搞清楚“我要的东西挂在哪个节点下面、有没有特殊的 class 可以复用”。3.2 获取页面并处理编码编码是新手最容易翻车的地方。正常的 UTF-8 页面直接用resp.text没问题但国内很多站点其实用的是 GBK 或 GB2312 编码。你直接打印resp.text看到一串乱码第一反应就应该是去检查resp.encoding。比较安全的做法是import requests url https://example.com/books headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0 Safari/537.36 } resp requests.get(url, headersheaders, timeout10) resp.encoding resp.apparent_encoding print(resp.status_code) print(resp.text[:500])resp.apparent_encoding会根据页面内容自动推断编码比直接写死utf-8或gbk要保险。注意这个推断过程可能会多花一点时间但对于学习阶段的脚本来说完全值得。3.3 提取数据并整理结构拿到正常文本后交给 BeautifulSoup 解析。我用 CSS 选择器来写简单直观from bs4 import BeautifulSoup soup BeautifulSoup(resp.text, lxml) items soup.select(div.book-item) for item in items: title item.select_one(h3.book-title).get_text(stripTrue) price item.select_one(span.price).get_text(stripTrue) link item.select_one(a).get(href) print(title, price, link)select_one是取第一个匹配节点和find类似。这里的逻辑是先在items这个列表里逐个取出每一本书的节点再在节点内部用 CSS 选择器精确定位各个字段。get_text(stripTrue)能去掉文本前后的空白和换行避免出现\n 书名 \n这种脏数据。3.4 完整代码与运行结果把上面两步拼起来一个基础的爬虫就成型了import requests from bs4 import BeautifulSoup def fetch_books(url): headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0 Safari/537.36 } resp requests.get(url, headersheaders, timeout10) resp.raise_for_status() resp.encoding resp.apparent_encoding soup BeautifulSoup(resp.text, lxml) books [] for item in soup.select(div.book-item): title item.select_one(h3.book-title).get_text(stripTrue) price item.select_one(span.price).get_text(stripTrue) link item.select_one(a).get(href) books.append({title: title, price: price, link: link}) return books if __name__ __main__: data fetch_books(https://example.com/books) for book in data: print(book)raise_for_status()是 requests 自带的“状态码检查器”状态码不是 200 时它会主动抛异常。这样你就不用手动if resp.status_code 200判断了代码更简洁。运行后输出格式类似{title: Python入门到放弃, price: 49.00, link: /books/1} {title: Requests实战, price: 59.00, link: /books/2}到了这一步你已经完成了一个最小闭环发请求、拿 HTML、解析、提取字段、输出结构化数据。4. 数据存储与工程化从打印到落库4.1 存储为 CSV 与 JSON打印到控制台只能算调试把结果保存成文件才是真正的产出。CSV 是最通用的格式Excel、WPS 都能直接打开。有一个细节用encodingutf-8-sig而不是utf-8因为带 BOM 的 UTF-8 在 Excel 里打开才不乱码。import csv with open(books.csv, w, newline, encodingutf-8-sig) as f: writer csv.writer(f) writer.writerow([title, price, link]) for book in data: writer.writerow([book[title], book[price], book[link]])如果你只是给自己程序用JSON 更友好import json with open(books.json, w, encodingutf-8) as f: json.dump(data, f, ensure_asciiFalse, indent2)ensure_asciiFalse必须写否则中文会被转成\uXXXX一堆转义字符肉眼根本没法看。4.2 使用 SQLite 与 SQLAlchemy 落库数据量大了之后文件存储就不够用了。查询、去重、增量更新这些操作用数据库才方便。对初学者来说 SQLite 是最好的起点因为它不需要单独安装数据库服务Python 内置支持一个文件就是整个库。如果你想把代码组织得更好可以直接上手 SQLAlchemy这是 Python 生态里最主流的 ORM 工具。定义一张表的代码非常清晰from sqlalchemy import create_engine, Column, Integer, String from sqlalchemy.orm import declarative_base, sessionmaker Base declarative_base() class Book(Base): __tablename__ books id Column(Integer, primary_keyTrue, autoincrementTrue) title Column(String(200)) price Column(String(50)) link Column(String(500)) engine create_engine(sqlite:///books.db) Base.metadata.create_all(engine) Session sessionmaker(bindengine) session Session()写入时逐个创建对象session.add_all([ Book(titlebook[title], pricebook[price], linkbook[link]) for book in data ]) session.commit()为什么要引入 ORM直接写 SQL 也能插数据但数据字段一变、表关系一复杂拼 SQL 就很容易出错。ORM 让你用 Python 对象的思路操作数据库同时避免手写字符串拼接带来的注入风险。学习阶段值得花半小时把它跑通。4.3 给脚本加点工程化函数拆分与日志很多新手写的爬虫脚本是一段从上到下的流水账能跑但很难维护。我建议从一开始就把代码拆成三个函数fetch_page负责请求parse_page负责解析save_data负责存储。这样改页面结构时只动parse_page改存储方式只动save_data互相不牵连。再加一行日志输出排查问题会轻松很多import logging logging.basicConfig(levellogging.INFO, format%(asctime)s - %(levelname)s - %(message)s) logging.info(开始抓取页面: %s, url)看起来不起眼但当你的脚本跑着跑着突然停了日志就是你判断问题卡在哪一步的唯一线索。5. 常见问题与排查技巧实录5.1 高频报错速查表报错信息出现原因解决办法requests.exceptions.ConnectionError网络不通或目标服务器拒绝连接检查网络确认 URL 拼写换http为https再试requests.exceptions.Timeout服务器响应太慢超过 timeout调大timeout增加重试逻辑HTTPError: 403 Forbidden被服务器反爬拦截补全 User-Agent加 cookies降低访问频率HTTPError: 404 Not FoundURL 不存在或需要登录才能访问检查 URL 是否拼接错确认是否需要登录态AttributeError: NoneType object has no attribute get_text选择器没匹配到节点回页面确认 class 名是否写错检查元素是否被反爬替换UnicodeEncodeError/ 控制台乱码编码不一致resp.encoding resp.apparent_encoding输出文件用utf-8-sig5.2 429 Too Many Requests请求太频繁被限流这个错误在真实抓取中太常见了。服务器返回429 Too Many Requests字面意思是请求太多触发了限流。我实测下来处理 429 的核心原则就一条尊重服务器给的节奏不要硬刚。很多网站会在响应头里带Retry-After字段明确告诉你“几秒后再来”。正确做法是读这个字段没读到就默认等待一个较长的间隔。import time import requests def safe_get(url, headers, max_retries3): for attempt in range(max_retries): resp requests.get(url, headersheaders, timeout10) if resp.status_code 429: retry_after int(resp.headers.get(Retry-After, 30)) logging.warning(触发限流等待 %s 秒后重试, retry_after) time.sleep(retry_after) continue resp.raise_for_status() return resp还有一个常见现象单线程循环很快时几十个请求瞬间打过去服务器立刻翻脸。我自己习惯在所有循环请求之间加time.sleep(1)到time.sleep(3)让请求节奏接近真人浏览。你要清楚任何页面资源都不是无限供给的采集速度越快对目标站点的压力越大被封的风险也越高。5.3 编码错乱与解析空结果的坑编码问题我在前面已经提过这里再补充一个场景有些网站响应头里声明的是utf-8实际内容却是 GBK。你直接用resp.text解码就偏了。这种情况resp.apparent_encoding往往更可靠因为它基于内容推断。解析不到节点是另一个高频问题。常见原因不是你代码写错而是目标元素是 JavaScript 动态渲染出来的。静态 HTML 里根本没有这本书名它是页面加载后由脚本填进去的那 BeautifulSoup 怎么都找不到。遇到这种情况先用print(resp.text)搜一下你要的关键字如果正文里根本不存在就说明数据不是服务端直出的需要另想思路。5.4 动态页面的初步绕过思路动态渲染页面是爬虫入门之后一定会撞上的墙。简单的判断方法在浏览器里右键查看源代码如果能看到要抓的内容那就是静态页面如果源代码里没有只有 Network 面板的 XHR 响应里有那就是接口动态加载。对动态页面第一优先级的思路是找它的数据接口。打开 Network 面板刷新页面盯着 XHR/JS 请求往往能找到一个返回 JSON 的接口地址。直接用 requests 抓这个接口比解析 HTML 更省事数据还是结构化格式连清洗过程都省了。如果找不到接口或者接口加密严重才需要考虑模拟浏览器。Selenium 是常见的方案它真实驱动一个浏览器环境让网站以为是真人访问。但代价是性能和效率大幅下降而且很多网站对无头浏览器也有检测。我的建议是能找接口就找接口Selenium 作为最后手段。6. 爬虫的边界与建议6.1 robots 协议与网站条款学爬虫除了写代码也要懂边界。每个正规站点一般都有robots.txt文件放在域名根路径下比如访问https://example.com/robots.txt就能看到。它声明了哪些路径允许爬虫访问、哪些路径禁止。入门阶段养成先看这个文件的习惯是最基本的技术素养。还要尊重网站的使用条款。很多数据有版权保护尤其是个人隐私、交易数据、付费内容不能因为技术上能爬到就随意抓取。我个人的底线是不抓数据用于商业竞争不抓涉及个人隐私的信息爬取频率控制在不对目标站点造成明显压力。6.2 频率控制与资源消耗你的爬虫对你自己来说是脚本对目标服务器来说是真实流量。一次正常的爬取如果并发开太高相当于瞬间给服务器制造了大量请求可能会拖慢站点甚至触发安全防御。入门阶段不要碰多线程、异步并发这些玩法单线程加延时已经够用。我之前写过一个批量采集脚本最初没有加 sleep两分钟就抓了上千页结果第 300 个请求开始就被连续限流最后整个 IP 被网站封了三天。后来学乖了每页间隔 1 到 2 秒稳定跑完全程。这件事让我彻底记住了爬虫不是竞速游戏活下来才是第一要务。6.3 学习路径建议Requests 和 BeautifulSoup 这对组合解决的是“静态页面抓取”这个核心场景它足够支撑你完成大多数入门练习。跑通本文这个示例后可以往几个方向扩展学会用Session维持登录态理解 cookie 的作用学会写更精细的 CSS 选择器学会把数据清洗和存储做得更规范再往后才是学习接口逆向、动态渲染页面处理、分布式采集这些进阶内容。每个方向都不简单但打好 Requests 和 BeautifulSoup 这个底子后面的路你会走得稳很多。个人体会是爬虫入门最大的障碍不是技术而是调试时的耐心。网页结构千奇百怪你的选择器写错一个 class结果就是一片空白。别急着怀疑人生回到浏览器开发者工具里重新看结构多试几次你就能慢慢摸清 HTML 的套路。这个“请求—解析—报错—修正”的循环本身就是爬虫最有价值的练习。先跑通一个小目标再慢慢扩展你会发现爬虫并没有想象中那么神秘。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑