很多学 Python 的新人最容易在“爬虫”这两个字上卡住。不是这门技术有多难而是大多数人打开教程之后先被 Requests 文档、BeautifulSoup 手册、XPath 语法、Selenium 配置这些零散知识点淹没了。学了一个月语法真正打开网页准备抓数据还是不知道第一行代码从哪里写。我的判断是爬虫入门最有效的路径不是背知识点而是沿着一条难度递增的实战案例线亲手把程序跑起来然后再改、再坏、再修。这篇文章整理了 18 个 Python 爬虫实战案例覆盖静态网页、JSON 接口、模拟登录、翻页、图片下载、批量采集等常用场景。每个案例都对应一个明确技能点难度从一颗星到四颗星递进非常适合零基础入门。读完这篇文章你会得到三样东西一套能直接修改使用的 Python 爬虫源码一份从请求到存储的完整技术地图以及一份能帮你少走半年弯路的排错和避坑清单。1. 这篇文章真正要解决的问题很多自学爬虫的读者通常会遇到下面三种典型困境困境一教程看懂了代码一运行就报错。比如requests.get()请求网页返回的不是 HTML 而是 JSON用BeautifulSoup定位元素class 名带空格导致提取为空中文打印出来乱码。这些问题不是语法不会而是缺少真实环境的应对经验。困境二只会复制别人的完整代码不会自己改。网上的完整源码大多耦合了特定网站换个目标页面就失效。你如果不理解“URL 构造—请求—解析—提取—存储”这条主链路就没有办法把别人的代码变成自己的工具。困境三遇到反爬就慌了。第一次请求被返回 403第一次遇到验证码第一次发现数据是异步加载的就以为爬虫学不下去了。实际上这些都有非常成熟的解决套路只是没人带着你系统过一遍。所以这篇教程要解决的核心问题不是“爬虫是什么”而是如何让一个只懂 Python 基础语法的人通过 18 个案例建立起完整的爬虫开发思维。案例从最简单的一个requests.get()开始到多线程批量采集收尾难度平缓上升。每一步都能运行、能看到输出、能知道为什么。适合阅读的人群Python 基础语法刚学完想找实战方向的新手会写for循环和函数但没独立写过爬虫的初学者曾经复制过爬虫代码但不知道如何扩展修改的开发者想系统掌握 requests、BeautifulSoup、lxml、CSV、SQLite 等工具链的读者。如果你已经是熟练使用 Scrapy 的工程师这篇文章的价值主要在案例分类和工程规范部分可以快速浏览。2. 爬虫核心概念与基础认知2.1 爬虫的本质是什么抛开各种复杂的术语爬虫的本质只有一句话用程序模拟浏览器向服务器发请求拿到响应后按规则提取出你需要的数据。浏览器能做的请求爬虫基本都能做浏览器不能做的爬虫通常也很难做。一个完整的爬虫流程必然包含四个环节请求构造 URL设置请求头和参数用requests或httpx把数据“要”回来。解析拿到 HTML 或 JSON 之后用BeautifulSoup、lxml、正则表达式或内置json模块提取目标内容。清洗去掉空格、换行、HTML 标签、重复项把数据整理成统一结构。存储写入 CSV、Excel、JSON 文件或保存到 SQLite、MySQL 数据库。很多新手抓不到数据多半是这四步之间出了问题而不是某一步不会写。2.2 批量型、增量型、垂直型爬虫的应用场景爬虫在工程上通常被分成三类理解它们的差异对设计自己的爬虫很有帮助类型核心特征典型场景技术重点批量型爬虫一次性把目标数据尽量完整抓下来迁移历史数据、导出全量商品信息并发控制、断点续爬、速度优化增量型爬虫只抓取上次之后新增或变化的数据新闻监测、价格监控、库存更新去重、时间戳记录、更新策略垂直型爬虫聚焦某一垂直领域深度解析结构化数据招聘信息、房源信息、论文元数据字段抽取、数据清洗、模板适配这篇文章的 18 个案例其实也是按照这三条线展开的。前 10 个偏“批量”基础中间 5 个解决“反爬”和增量更新最后 3 个触及多线程和完整爬虫雏形。2.3 常用爬虫工具库对比库/工具作用适用阶段学习成本requests发送 HTTP 请求入门必学低BeautifulSoup4解析 HTML提取数据入门必学低lxml高性能 HTML/XML 解析支持 XPath进阶推荐中正则表达式字符串级匹配提取辅助手段中Selenium模拟真实浏览器处理复杂渲染动态页面高Scrapy完整爬虫框架工程化项目高新手不需要一开始就把这些全学会。先把 requests BeautifulSoup 用熟能覆盖 80% 的静态页面和 JSON 接口场景。Selenium 和 Scrapy 等有需要时再学效率更高。3. 环境准备与 Python 开发环境搭建3.1 安装 Python建议使用 Python 3.8 及以上版本推荐 3.9 或 3.10稳定性更好。到 Python 官网下载对应系统的安装包安装时务必勾选“Add Python to PATH”。安装完成后打开命令行工具验证python --version如果显示出版本号说明安装成功。在 macOS 或 Linux 上可能需要使用python3命令。3.2 pip 换源国内直接使用官方 PyPI 源下载依赖库经常很慢建议换成国内镜像源。以清华源为例在命令行执行pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple pip config set global.trusted-host pypi.tuna.tsinghua.edu.cn换源之后pip install的下载速度会明显提升。3.3 安装爬虫依赖库本文后续案例需要用到以下库可以一次性安装pip install requests beautifulsoup4 lxml pandas其中requests负责发送 HTTP 请求beautifulsoup4用于解析 HTMLlxml作为 BeautifulSoup 的解析引擎也可以直接用 XPathpandas方便数据整理和 CSV 导出。如果在安装过程中遇到权限问题可以在命令前加pip install --user或使用虚拟环境。3.4 开发环境推荐VS Code安装 Python 插件和 Python extension pack轻量、免费。PyCharm Community Edition适合不想折腾环境的新手直接创建项目即可。Jupyter Notebook适合做数据观察和调试但不适合跑长期爬虫任务。不管用哪个工具建议在项目目录下新建一个crawler文件夹把案例脚本按编号保存方便之后对照和复用。4. 18 个 Python 爬虫实战案例全景拆解下面按难度把 18 个案例分成四个梯度。每个案例都对应一个明确的技能点强烈建议按顺序做不要跳。前一个案例的方法往往是后一个案例的基础。4.1 梯度一静态网页解析与基础请求案例编号案例名称核心技能点涉及库难度01抓取网页标题与段落文本requests 基础请求、BeautifulSoup 文本提取requests, bs4★02抓取新闻列表标题和链接CSS 选择器定位、get_text 使用requests, bs4★03抓取表格数据并导出 CSV表格标签解析、pandas 存储requests, bs4, pandas★★04分页列表数据批量采集URL 翻页参数构造、循环请求requests, bs4★★05批量下载网页图片图片链接提取、二进制文件保存requests, bs4★★06爬取页面内所有超链接href 属性提取、链接去重requests, bs4★★这一梯队的核心任务是把静态 HTML 页面里能看到的数据拿下来。完成 01 到 06你就掌握了最基础的爬虫主链路。4.2 梯度二JSON 接口与动态数据案例编号案例名称核心技能点涉及库难度07抓取 JSON 接口数据response.json()解析、字段筛选requests★★08分析异步加载的 XHR 请求浏览器开发者工具抓取接口requests★★★09按参数动态请求数据构造请求参数、处理分页参数requests★★★10数据清洗与字段规整去空格、去重复、统一格式pandas / 内置方法★★★很多网站表面上是静态页面实际上数据是前端通过 Ajax 请求 JSON 接口动态渲染的。这一梯队的案例教会你脱离页面源码直接向接口要数据效率会高出很多。4.3 梯度三会话登录与反爬基础案例编号案例名称核心技能点涉及库难度11表单登录并维持 Sessionrequests.Session与表单提交requests★★★12携带 Cookie 请求登录后页面Cookie 提取、请求头构造requests★★★13自定义 User-Agent 绕过简单反爬UA 伪装、请求头完善requests★★★14随机延时与请求频率控制time.sleep随机延时策略time / random★★★15使用代理池绕过 IP 限制代理配置、代理切换策略requests★★★★注意登录类案例只建议在自己有账号、有授权的平台实践。爬取需要登录的私人数据必须确保自己拥有访问权限且不违反平台规则。4.4 梯度四批量、增量与垂直爬虫进阶案例编号案例名称核心技能点涉及库难度16多线程批量爬虫ThreadPoolExecutor并发请求concurrent.futures★★★★17增量型爬虫设计基于去重集合和文件记录实现增量更新requests 文件操作★★★★18垂直型爬虫完整实战组合以上所有技能的完整爬虫项目requests, bs4, sqlite3★★★★到这一梯队你已经不是在“跑通”脚本了而是在“设计”一个小型爬虫系统。多线程控制、增量策略、数据落库这些能力可以直接复用到工作项目中。5. 核心案例代码实现附源码下面给出几个最核心案例的完整源码。代码以可运行、可修改为原则URL 统一使用示例地址你需要替换成实际目标页面。5.1 通用请求函数模板这个函数是整个 18 个案例的地基建议创建utils.py文件保存它。# 文件路径crawler/utils.py import time import random import requests from requests.adapters import HTTPAdapter def get_headers(): 构造一个常见的请求头模拟浏览器访问。 return { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36, Accept: text/html,application/xhtmlxml,application/xml;q0.9,*/*;q0.8, Accept-Language: zh-CN,zh;q0.9,en;q0.8, } def fetch_url(url, headersNone, timeout10, retries3): 通用请求函数支持超时、重试、自动识别编码。 session requests.Session() session.mount(http://, HTTPAdapter(max_retriesretries)) session.mount(https://, HTTPAdapter(max_retriesretries)) if headers is None: headers get_headers() response session.get(url, headersheaders, timeouttimeout) response.raise_for_status() # 优先使用网页声明的编码否则按内容猜测避免中文乱码 if response.encoding is None or response.encoding.lower() iso-8859-1: response.encoding response.apparent_encoding return response def random_sleep(min_seconds0.5, max_seconds2.0): 随机休眠一段时间降低对目标服务器的请求压力。 time.sleep(random.uniform(min_seconds, max_seconds))关键逻辑说明HTTPAdapter(max_retriesretries)可以在连接失败时自动重试避免单次网络抖动导致整个脚本中断response.encoding设置不当会导致中文乱码所以增加自动识别逻辑random_sleep是控制请求频率的基础工具几乎所有案例都会用到。5.2 案例 01抓取网页标题与段落文本# 文件路径crawler/case_01_title_paragraph.py import sys from bs4 import BeautifulSoup from utils import fetch_url, get_headers def parse_page(url): 抓取页面标题和所有段落文本。 response fetch_url(url, headersget_headers()) soup BeautifulSoup(response.text, lxml) title soup.title.get_text(stripTrue) if soup.title else 无标题 print(页面标题, title) paragraphs soup.find_all(p) for index, p in enumerate(paragraphs[:10], start1): text p.get_text(stripTrue) if text: print(f段落 {index}: {text}) if __name__ __main__: # 使用示例地址实际爬取时替换为目标网页 target_url https://example.com/ if len(sys.argv) 1: target_url sys.argv[1] parse_page(target_url)运行方式python case_01_title_paragraph.py https://example.com/如何判断成功终端会输出页面标题和最多 10 个非空段落。如果输出为空优先检查页面是否为动态加载或者选择器是否正确。5.3 案例 07抓取 JSON 接口数据很多网页的数据来自独立接口直接请求接口比解析 HTML 更高效。# 文件路径crawler/case_07_json_api.py import json from utils import fetch_url, get_headers, random_sleep def crawl_json_api(api_url, page1): 从 JSON 接口获取数据并打印指定字段。 # 常见接口参数分页信息放在 query 中 params { page: page, pageSize: 20, } response fetch_url(api_url, headersget_headers()) # 如果接口支持 GET 参数可以这样传参 # response fetch_url(f{api_url}?page{page}pageSize20, headersget_headers()) try: data response.json() except json.JSONDecodeError: print(响应不是合法 JSON先检查 URL 或响应内容) return [] # 这里假设返回结构为 {code:0, data: {list:[]}} items data.get(data, {}).get(list, []) for item in items: print(fID: {item.get(id)}, 标题: {item.get(title)}) return items if __name__ __main__: # 示例接口地址实际爬取时请替换为真实接口 api_url https://api.example.com/list crawl_json_api(api_url, page1)重点提醒分析接口时打开浏览器开发者工具的 Network 面板刷新页面找到返回数据含有目标内容的 XHR 请求把它的 URL 复制出来。接口 URL 通常比 HTML 域名多一个/api路径参数也更规范。5.4 案例 04翻页爬虫完整示例翻页是爬虫最常见的需求。核心是构造每一页的 URL 或请求参数。# 文件路径crawler/case_04_pagination.py from bs4 import BeautifulSoup from utils import fetch_url, get_headers, random_sleep def crawl_page(page_num): 抓取某一页的数据。 # 示例翻页模式https://example.com/list?page1 url fhttps://example.com/list?page{page_num} response fetch_url(url, headersget_headers()) soup BeautifulSoup(response.text, lxml) items [] for node in soup.select(div.item): title_node node.select_one(a) if not title_node: continue title title_node.get_text(stripTrue) link title_node.get(href) items.append({page: page_num, title: title, link: link}) return items def crawl_all(start_page, end_page): 抓取 start_page 到 end_page 的数据。 all_data [] for page in range(start_page, end_page 1): print(f正在抓取第 {page} 页...) page_data crawl_page(page) all_data.extend(page_data) # 控制请求频率避免被目标网站拒绝 random_sleep() return all_data if __name__ __main__: result crawl_all(1, 5) print(f共抓取 {len(result)} 条数据) for item in result[:3]: print(item)关键点翻页参数不一定是page也可能是pn、pageNo、offset需要先观察 URL 变化规律有些网站第一页的 URL 和第二页不同测试时不要想当然random_sleep()控制请求频率这是爬虫最基本的礼貌也是防封号最简单的手段。5.5 案例 05批量下载网页图片# 文件路径crawler/case_05_download_images.py import os from urllib.parse import urljoin from bs4 import BeautifulSoup from utils import fetch_url, get_headers, random_sleep def download_images(page_url, save_dirimages): 提取页面中所有 img 标签的图片并保存到本地。 os.makedirs(save_dir, exist_okTrue) response fetch_url(page_url, headersget_headers()) soup BeautifulSoup(response.text, lxml) img_tags soup.find_all(img) print(f找到 {len(img_tags)} 个图片标签) downloaded 0 for index, img in enumerate(img_tags): src img.get(src) or img.get(data-src) if not src: continue # 相对路径转绝对路径 img_url urljoin(page_url, src) # 跳过占位图和图标 if data:image in img_url or logo in img_url.lower(): continue try: img_response fetch_url(img_url, headersget_headers()) if img_response.status_code ! 200: continue # 根据 Content-Type 推断扩展名 content_type img_response.headers.get(Content-Type, ) ext .jpg if jpeg in content_type else .png if png in content_type else .jpg filename os.path.join(save_dir, fimage_{index 1}{ext}) with open(filename, wb) as f: f.write(img_response.content) downloaded 1 print(f已下载: {filename}) random_sleep(0.3, 1.0) except Exception as exc: print(f下载失败: {img_url}, 错误: {exc}) print(f全部完成共下载 {downloaded} 张图片) if __name__ __main__: download_images(https://example.com/gallery)提醒图片下载会消耗目标服务器带宽控制并发和频率不要对同一站点发起大规模下载。下载后的图片要关注版权问题。5.6 数据存储CSV 与 SQLiteCSV 存储# 文件路径crawler/storage_csv.py import csv def save_to_csv(data, filenameoutput.csv): data: list[dict]如 [{title: xxx, link: url}] if not data: print(没有数据可保存) return fieldnames list(data[0].keys()) with open(filename, w, newline, encodingutf-8-sig) as f: writer csv.DictWriter(f, fieldnamesfieldnames) writer.writeheader() writer.writerows(data) print(f数据已保存到 {filename})SQLite 存储# 文件路径crawler/storage_sqlite.py import sqlite3 def init_db(db_pathdata.db): conn sqlite3.connect(db_path) cursor conn.cursor() cursor.execute( CREATE TABLE IF NOT EXISTS items ( id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT, link TEXT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) ) conn.commit() return conn def save_to_sqlite(data, db_pathdata.db): conn init_db(db_path) cursor conn.cursor() for item in data: cursor.execute( INSERT INTO items (title, link) VALUES (?, ?), (item.get(title), item.get(link)) ) conn.commit() conn.close() print(f已写入 {len(data)} 条数据到 SQLite)SQLite 的好处是文件型数据库不需要安装任何服务端适合爬虫项目做本地存储和增量去重。5.7 案例 16多线程批量爬虫# 文件路径crawler/case_16_thread_pool.py from concurrent.futures import ThreadPoolExecutor, as_completed from utils import fetch_url, get_headers, random_sleep def crawl_one_url(url): 单个 URL 的抓取任务。 try: response fetch_url(url, headersget_headers()) length len(response.text) return {url: url, status: response.status_code, length: length} except Exception as exc: return {url: url, status: ERROR, length: str(exc)} def batch_crawl(urls, max_workers5): 使用线程池并发抓取多个 URL。 results [] with ThreadPoolExecutor(max_workersmax_workers) as executor: future_map {executor.submit(crawl_one_url, url): url for url in urls} for future in as_completed(future_map): result future.result() results.append(result) print(result) return results if __name__ __main__: test_urls [ https://example.com/page/1, https://example.com/page/2, https://example.com/page/3, https://example.com/page/4, https://example.com/page/5, ] batch_crawl(test_urls, max_workers3)注意多线程可以大幅提升速度但也会成倍增加目标服务器的压力。新手建议先把单线程跑通确认数据没问题后再上多线程。线程数从3开始不要一上来就开到 20。6. 运行结果与效果验证6.1 怎么确认脚本成功判断爬虫脚本是否成功不能只看“没有报错”还要检查数据和预期是否一致。数量验证抓取到的条数是否和页面显示的总数吻合。字段验证标题、链接、日期等字段是否完整是否出现空值或乱码。重复验证翻页采集后是否有多条重复数据。落库验证CSV 文件是否能正常打开SQLite 表里记录数是否符合预期。6.2 日志与中间结果打印不要把脚本写成“黑盒”。建议在关键位置打印进度print(f正在抓取第 {page} 页累计 {len(all_data)} 条) print(f保存完成共 {len(data)} 条记录)如果脚本运行时间较长还可以用logging模块把日志输出到文件方便排查。6.3 失败排查顺序脚本运行失败时不要急着一行一行改代码按下面顺序排查看请求是否成功打印response.status_code如果是 403 说明被反爬了如果是 404 说明 URL 有问题。看响应内容是否正确打印response.text[:500]确认是否拿到 HTML 或 JSON。看解析层是否报错如果 BeautifulSoup 提取结果为空先检查选择器。看数据是否写入失败检查存储文件路径和数据库表结构。7. Python 爬虫常见问题与排查方法问题现象可能原因排查方式解决方案请求返回 403服务器识别出非浏览器请求打印响应头和状态码伪造完整请求头重点是 User-Agent 和 Referer中文乱码编码判断错误打印response.encoding设置response.encoding utf-8或使用apparent_encodingBeautifulSoup 提取为空选择器写错或数据是动态加载打印soup.prettify()检查 HTML核对 class 名使用浏览器开发者工具复制选择器动态数据改用接口请求JSON 解析失败接口返回的不是 JSON 而是 HTML打印原始响应文本检查 URL 是否错误或接口需要额外请求头请求超时网络波动或目标网站响应慢查看错误堆栈增加timeout参数配置HTTPAdapter重试翻页数据重复URL 参数没有随页码变化对比第 1 页和第 2 页 URL找出翻页参数并动态构造图片下载为空图片是懒加载真实地址在>import random USER_AGENTS [ Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36, Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36, Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0, ] def get_random_ua(): return random.choice(USER_AGENTS)8.3 限速与重试策略在爬虫中稳定比速度重要得多。建议每次请求之间随机延时 0.5 到 2 秒请求失败时使用指数退避策略重试不要对同一域名发起持续的并发高负载请求。8.4 数据去重与增量更新增量型爬虫的核心是记录“已经处理过的标记”。常用的方法用集合保存已抓取的 URL 或内容哈希用 SQLite 表记录url_hash并建立唯一索引用文件记录“上次抓取的最后时间戳”。每次抓取前先判断是否已存在存在就跳过。这是从“批量爬虫”升级到“增量爬虫”的关键一步。8.5 配置与敏感信息管理不要把账号密码、Cookie、数据库连接串直接硬编码在代码里。建议使用环境变量或配置文件import os username os.getenv(CRAWLER_USERNAME, ) password os.getenv(CRAWLER_PASSWORD, )如果已经写进代码并提交到仓库即使只是个人项目也建议轮换密码。8.6 从脚本到工程的演进爬虫学习到一定阶段自然要面对代码组织问题。可以按下面的复杂度逐步演进单文件脚本适合学习和一次性任务拆分工具层把请求、解析、存储分别放到独立模块引入配置文件URL、参数、延时策略全部可配置引入任务队列或数据库适合大规模、持续运行的项目使用 Scrapy 框架当爬虫需要分布式、中间件、Pipeline 时Scrapy 是成熟的选择。9. 总结与后续学习方向现在回头再看这 18 个案例你会发现自己已经走过了四条关键的路静态页面解析、JSON 接口爬取、会话与反爬基础、批量与增量爬虫设计。这些能力正是“能从零写一个爬虫”和“只会复制代码”之间的分界线。对刚入门的朋友建议回头用两个问题检验自己不打开文档能不能独立写出带异常处理、编码处理和限速的请求函数面对一个从没见过的网站能不能用浏览器开发者工具分析出数据来源如果这两个问题都能答上来说明你已经具备了独立爬取数据的核心能力。接下来的进阶路线可以按照自己的兴趣选择想做好大规模数据采集学习 Scrapy想处理复杂动态页面研究 Selenium 和 Playwright想提升采集效率学习异步爬虫想系统落地增量更新与数据治理深入研究数据库设计与任务调度。这些方向每一个都能写出一篇长文但所有方向的地基都在这 18 个案例里。建议把这篇文章收藏备用先在本地把代码跑通再尝试把 URL 替换成自己感兴趣的页面。只有真正运行起来、出过错、改对过爬虫这门技术才算真正长在了你自己身上。