Python爬虫实战:Requests+BeautifulSoup实现批量数据采集与导出
这次我们来看一个 Python 爬虫实战项目。很多读者收藏了一堆爬虫教程但真正能跑起来、能落地到工作里的并不多。这篇文章不绕弯子直接给一套完整的 Python 爬虫入门方案从网页请求、HTML 解析、数据清洗到批量导出附带可运行的源码结构。需要先说明一点爬虫技术本身是合法的数据处理工具但使用边界非常明确。不要利用爬虫绕过付费墙、会员验证、版权保护机制不要采集非公开数据不要对目标站点造成压力。本文的所有示例都基于公开页面和公开接口目标是帮你掌握 Requests BeautifulSoup 正则表达式 JSON 数据处理的完整链路。整套代码我用一个实际可运行的案例来讲抓取一个公开示例站点的数据解析标题、摘要、链接保存为 JSON 和 CSV支持多页批量采集。同时会讲解如何设计请求间隔、如何做异常重试、如何观察资源占用。先看核心能力速览再逐步部署和验证。1. 核心能力速览能力项说明项目类型Python 爬虫入门实战脚本核心功能网页请求、HTML 解析、数据提取、JSON/CSV 批量导出技术栈Python 3、Requests、BeautifulSoup4、lxml、pandas启动方式命令行运行是否支持批量任务支持多页循环抓取是否支持 API 服务本次脚本不涉及服务端 API但请求逻辑可复用硬件要求普通电脑即可无需 GPU操作系统Windows / Linux / macOS 均可适合人群Python 入门者、数据分析初学者、需要做公开数据采集的技术人员从材料看这个项目的重点是“完整跑通爬虫流程”不是复杂框架。适合先用小体量代码建立整体认知再根据业务需求扩展。2. 适用场景与使用边界2.1 适合什么场景爬虫脚本最常见的落地场景是公开数据的批量整理。比如采集公开新闻列表提取标题、发布时间、链接。采集公开商品信息用于比价分析。采集公开学术论文元数据。采集公开招聘信息、天气数据、行业报告目录。对接公开 API把返回的 JSON 存到本地数据库。这类任务的特点是数据本身可公开访问、不需要登录、不涉及个人隐私、不绕过访问控制。2.2 不适合什么场景需要登录才能看到的付费内容、会员专享内容不能绕行采集。禁止爬虫的站点不能强行抓取。涉及个人隐私、账号信息、版权保护的内容不碰。高频率、大并发抓取会导致服务器过载不适合。有明确 robots.txt 限制或服务条款禁止采集的站点必须遵守。2.3 合规底线无论源码结构多完整都必须记住爬虫只是工具使用权限取决于目标站点的公开授权。人脸、声音、肖像、版权作品、商业付费内容的采集和二次使用务必确认授权。本文给出的所有代码只用于学习和验证公开数据采集流程。3. 环境准备与前置条件3.1 操作系统项目不依赖特殊系统调用Windows 10/11、Ubuntu 20.04、macOS 12 都可以。3.2 Python 版本推荐使用 Python 3.8 以上版本。Windows 用户安装时勾选“Add Python to PATH”避免后续命令行找不到 python。检查版本python --version如果出现命令不存在尝试python3 --version3.3 依赖库本项目的核心依赖是requests发起 HTTP 请求。beautifulsoup4解析 HTML。lxmlBeautifulSoup 的解析器速度快。pandas可选用于生成 CSV。安装命令pip install requests beautifulsoup4 lxml pandas如果下载速度慢可以加国内镜像源pip install requests beautifulsoup4 lxml pandas -i https://pypi.tuna.tsinghua.edu.cn/simple3.4 网络环境目标站点必须能正常访问。测试时建议先用浏览器打开目标 URL确认内容是公开可见的。如果浏览器都打不开那脚本大概率也拿不到数据。3.5 代码管理建议新建一个项目目录例如python-spider-demo/ ├── spider.py ├── requirements.txt ├── output/ │ ├── data.json │ └── data.csv └── logs/ └── spider.log项目目录这样拆分后续扩展、调试、清理输出都比较方便。4. 安装部署与启动方式4.1 创建虚拟环境虚拟环境能隔离依赖避免不同项目的包版本冲突。Windowspython -m venv venv venv\Scripts\activateLinux/macOSpython3 -m venv venv source venv/bin/activate激活后命令行前缀会显示(venv)。4.2 安装依赖在项目目录生成 requirements.txtrequests2.31.0 beautifulsoup44.12.2 lxml4.9.3 pandas2.0.3安装pip install -r requirements.txt4.3 编写基础爬虫脚本下面给出一个完整可运行的示例脚本。目标站点使用公开的示例页面实际使用时需要按目标站点的 HTML 结构调整选择器和 URL 规则。import requests import json import time import logging import csv from bs4 import BeautifulSoup logging.basicConfig( levellogging.INFO, format%(asctime)s - %(levelname)s - %(message)s, handlers[ logging.FileHandler(logs/spider.log, encodingutf-8), logging.StreamHandler() ] ) 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 } def fetch_page(url): 获取页面 HTML try: response requests.get(url, headersHEADERS, timeout10) response.raise_for_status() response.encoding response.apparent_encoding return response.text except requests.RequestException as e: logging.error(f请求失败: {url}, 错误: {e}) return None def parse_page(html): 解析页面提取数据列表 if not html: return [] soup BeautifulSoup(html, lxml) # 假设页面结构中有 .item 列表项每项包含 .title 和 .summary items soup.select(.item) data [] for item in items: title_tag item.select_one(.title) summary_tag item.select_one(.summary) link_tag item.select_one(a) title title_tag.get_text(stripTrue) if title_tag else summary summary_tag.get_text(stripTrue) if summary_tag else link link_tag.get(href) if link_tag else if title: data.append({ title: title, summary: summary, link: link }) return data def save_json(data, filepathoutput/data.json): 保存为 JSON with open(filepath, w, encodingutf-8) as f: json.dump(data, f, ensure_asciiFalse, indent2) logging.info(fJSON 已保存: {filepath}, 数据条数: {len(data)}) def save_csv(data, filepathoutput/data.csv): 保存为 CSV if not data: logging.warning(无数据可保存) return with open(filepath, w, encodingutf-8-sig, newline) as f: writer csv.DictWriter(f, fieldnames[title, summary, link]) writer.writeheader() writer.writerows(data) logging.info(fCSV 已保存: {filepath}, 数据条数: {len(data)}) def main(): base_url https://example.com/list?page{} # 替换为实际公开页面 all_data [] for page in range(1, 4): # 抓取前 3 页 url base_url.format(page) logging.info(f开始抓取: {url}) html fetch_page(url) items parse_page(html) all_data.extend(items) time.sleep(2) # 请求间隔降低目标站点压力 save_json(all_data) save_csv(all_data) if __name__ __main__: main()这段代码包含了几个关键设计headers 设置了 User-Agent模拟浏览器请求。每次请求之间 sleep 2 秒避免高频请求。日志同时输出到控制台和文件方便定位问题。异常处理保证了单页失败时程序不会整体退出。4.4 启动脚本在项目目录下运行python spider.py如果一切正常控制台会输出“开始抓取”“JSON 已保存”“CSV 已保存”等日志output 目录下会生成 data.json 和 data.csv。5. 功能测试与效果验证5.1 测试请求是否成功在写爬虫之前先单独验证目标网址能否通过 Requests 访问。可以临时写一个测试脚本import requests url https://example.com # 替换为实际公开页面 headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) } resp requests.get(url, headersheaders, timeout10) print(状态码:, resp.status_code) print(页面长度:, len(resp.text))判断标准状态码为 200说明页面可访问。状态码为 403/418说明站点有反爬策略。页面长度过短可能返回了验证码或拦截页。5.2 测试 HTML 解析逻辑在 Requests 能成功拿到 HTML 后需要验证选择器是否匹配目标页面的实际结构。操作步骤浏览器打开目标页面按 F12 打开开发者工具。在 Elements 面板定位要采集的数据区域。观察列表项是否有统一的 class 或标签结构。把.item、.title、.summary替换成页面中真实存在的选择器。例如目标页面结构是div classpost h2 classpost-titlea href/post/1标题/a/h2 p classpost-desc摘要内容/p /div那解析选择器就改为items soup.select(.post) title_tag item.select_one(.post-title a) summary_tag item.select_one(.post-desc) link title_tag.get(href) if title_tag else 5.3 批量多页测试把 main 函数里的循环页数改为 2 到 3 页跑一次看稳定性是否每页都能正常解析。是否有重复数据。是否有空字段。目标站点是否出现访问限制。如果出现连续失败检查是否触发了反爬策略。请求间隔是否太短。是否缺少必要的 Cookie 或 Referer。5.4 判断是否成功的标准输出 JSON 能正常打开中文不乱码。CSV 用 Excel 打开不乱码UTF-8-sig 编码已处理。数据条数和预期一致。日志中没有大面积请求异常。5.5 常见失败原因现象可能原因返回 403缺少 User-Agent 或触发反爬返回空列表选择器和页面结构不匹配中文乱码编码设置不正确改用 apparent_encoding数据重复页面存在动态加载或翻页参数有误页面内容为空数据通过 JavaScript 动态渲染6. 接口 API 与批量任务这个项目本身不涉及服务端 API但爬虫落地到业务中时通常会遇到两个需求一是采集目标站点的公开接口二是把爬虫改造成可批量调度的任务。6.1 直接请求公开 JSON 接口很多网站的数据其实通过 JSON 接口返回不需要解析 HTML。比如公开的新闻列表接口返回结构类似{ code: 0, message: success, data: { list: [ {id: 1, title: 标题1, url: /news/1}, {id: 2, title: 标题2, url: /news/2} ], page: 1, pagesize: 10, total: 100 } }Python 端可以直接请求并解析 JSONimport requests import json API_URL https://example.com/api/news?page{} def fetch_json(page): url API_URL.format(page) resp requests.get(url, headersHEADERS, timeout10) resp.raise_for_status() return resp.json() def main(): all_news [] for page in range(1, 4): result fetch_json(page) items result[data][list] all_news.extend(items) print(page, len(items)) with open(output/news.json, w, encodingutf-8) as f: json.dump(all_news, f, ensure_asciiFalse, indent2) if __name__ __main__: main()这种方式比解析 HTML 更稳、更快。前提是确认该接口允许访问并且没有在服务条款中被禁止。6.2 批量任务设计实际项目中批量爬虫要考虑的不只是循环页码还需要考虑任务列表是 URL 列表还是关键词列表还是时间区间列表。去重逻辑重复的数据不重复入库。失败重试单次请求失败后自动重试 2 到 3 次。日志记录每条任务的开始时间、结束时间、状态、数据条数。限速策略控制每秒请求数避免影响目标服务。简单重试示例def fetch_with_retry(url, max_retry3): for attempt in range(max_retry): try: resp requests.get(url, headersHEADERS, timeout10) resp.raise_for_status() return resp.text except Exception as e: logging.warning(f第 {attempt 1} 次请求失败: {e}) time.sleep(2 * (attempt 1)) return None6.3 任务队列化如果数据量上升到几千条可以引入任务队列概念。不需要复杂框架用 Python 自带的队列即可import queue import threading task_queue queue.Queue() results [] URL_LIST [fhttps://example.com/list?page{i} for i in range(1, 11)] def worker(): while not task_queue.empty(): url task_queue.get() try: html fetch_page(url) items parse_page(html) results.extend(items) finally: task_queue.task_done() for url in URL_LIST: task_queue.put(url) threads [] for i in range(3): # 3 个线程 t threading.Thread(targetworker) t.start() threads.append(t) task_queue.join()注意多线程会加快采集速度但也会增加目标站点的压力。实际使用时要控制线程数并遵守目标站点的访问频率要求。7. 资源占用与性能观察爬虫脚本的资源占用主要取决于请求并发数和响应体大小。7.1 显存与 GPU本项目不涉及 GPU 计算不占用显存。普通笔记本即可运行。7.2 CPU 占用单线程请求时CPU 占用很低主要开销在 HTML 解析。lxml 解析器比 html.parser 快建议保留。多线程模式下CPU 占用会上升但一般不会超过 30%。7.3 内存占用一次性把所有数据存储在列表里数据量大会占内存。如果抓取几万条数据建议用数据库或流式写入不要全部攒在内存里。7.4 如何观察占用Windows 打开任务管理器切到“性能”标签页观察 CPU、内存、网络。Linux 可以使用top -p $(pgrep -f spider.py)7.5 降低资源占用的方法缩小解析范围只获取页面中需要的数据节点。启用 Session 复用连接减少握手开销。控制线程数不盲目开多线程。数据量多时解析完一批就写入磁盘释放内存。示例session requests.Session() session.headers.update(HEADERS) def fetch_page_session(url): resp session.get(url, timeout10) return resp.text7.6 避免端口冲突与进程残留如果后续把爬虫改造成 Web 服务要注意启动时指定端口并检查占用。Windows 查看端口占用netstat -ano | findstr 8000Linux/macOSlsof -i :8000本文的脚本是命令行任务不涉及长期占用的服务端口。8. 常见问题与排查方法8.1 问题排查表问题现象可能原因排查方式解决方案pip 安装依赖失败网络问题或 Python 版本过旧查看 pip 报错信息换镜像源或升级 Python启动后无输出没安装依赖或代码有语法错误运行python spider.py看报错按报错安装依赖或修复语法返回状态码 403缺少 User-Agent 或被反爬拦截浏览器打开链接对比设置完整请求头降低请求频率返回状态码 404页面路径不对浏览器访问确认修正 URL 规则解析结果为空页面结构变化或选择器错误用开发者工具检查 HTML调整 CSS 选择器中文乱码编码识别错误查看页面源码 charset使用 apparent_encoding 或强制指定编码数据重复翻页参数或去重逻辑缺失打印页 URL 和数据条数增加 URL 去重或数据库约束批量任务卡住某次请求长时间无响应查看日志定位超时页面设置 timeout 参数增加重试机制CSV 打开乱码编码用了 UTF-8用记事本或 Excel 打开写入使用 utf-8-sig反爬验证码高频请求触发风控观察请求间隔增加 sleep随机化请求时间8.2 依赖安装失败Windows 下常见报错是“No matching distribution found for XXX”。处理方式pip install requests beautifulsoup4 lxml pandas -i https://pypi.tuna.tsinghua.edu.cn/simplemacOS 如果 lxml 安装失败可以尝试xcode-select --install8.3 页面结构变化导致代码失效这是爬虫最常见的维护问题。同一个页面的 class 名可能改版推荐做法选择器不要写死太多层级。尽量使用稳定的属性定位。关键选择器集中放在脚本顶部方便统一修改。8.4 请求时间超时统一给 requests 调用加 timeout 参数避免线程卡死resp requests.get(url, headersHEADERS, timeout10)单个请求超过 10 秒就抛异常由重试逻辑处理。9. 最佳实践与使用建议9.1 第一次先小范围验证不要一次性抓几千页。先抓 1 到 3 页确认数据格式、稳定性、目标站点的响应情况再扩大范围。9.2 保留一套最小可运行配置把下面几项独立成配置方便切换base_urlheaderstimeoutsleep_timestart_pageend_page例如CONFIG { base_url: https://example.com/list?page{}, headers: {User-Agent: Mozilla/5.0}, timeout: 10, sleep_time: 2, start_page: 1, end_page: 3, output_dir: output }9.3 数据分目录管理输入、输出、日志分开project/ ├── config.py ├── spider.py ├── requirements.txt ├── output/ │ ├── data.json │ └── data.csv └── logs/ └── spider.log商品数据、文章数据、每日增量数据按日期存储output/2026-05-01/ output/2026-05-02/9.4 批量任务加日志和失败重试每个页面抓取完成后记录耗时、成功数量、失败原因。重试逻辑控制在 2 到 3 次避免无意义循环。9.5 接口服务要限制访问范围如果后续把爬虫能力封装成接口要加访问控制和频率限制不对外暴露内部接口防止被滥用。9.6 版权与授权爬虫采集到的数据不能随意用于商业发布。特别是涉及影视、音乐、文章、图片、人脸、声音等信息时必须确认原权利人是否授权。没有明确授权的数据不要用于公开展示、二次分发或商用。9.7 发布或商用前要做效果复核数据字段是否完整。数据时间范围是否准确。是否有重复记录。是否有被反爬干扰导致的遗漏。是否已按法律法规和隐私政策处理。10. 总结与下一步这次给出的 Python 爬虫源码核心价值不是“一键抓取”而是帮你把整套链路跑通Requests 发起请求、BeautifulSoup 解析 HTML、JSON/CSV 导出、多页批量采集、日志记录、失败重试。建议先收藏然后按以下顺序操作在本地建好虚拟环境安装依赖。用目标公开页面替换示例 URL。先用 1 页验证解析逻辑再扩展到多页。观察日志和多页稳定性。把请求间隔、重试次数、输出目录等参数独立配置化。最容易踩的坑是页面选择器不匹配和请求被反爬拦截前者靠 F12 调试后者靠降低频率和补充请求头解决。后续你的扩展方向有三个爬虫调度引入定时任务每天定点抓取公开数据。数据持久化把输出从 JSON/CSV 升级为 SQLite 或 MySQL支持增量更新。接口化复用把抓取逻辑封装成服务供内部数据分析和自动化流程调用。爬虫学习的关键不在于“抓得多快”而在于“数据拿得稳、格式清晰、可维护、不越界”。这篇源码可以作为你长期复用的基础模板。