去年帮朋友做一个竞品价格监控的项目,目标网站是个电商平台,商品价格在前端看不到,全藏在JS动态加载的接口里。用requests请求回来的HTML源码打开一看,价格那个位置是个空的div标签,啥也没有。当时排查了半天才发现,数据是页面加载完成后通过AJAX异步请求拉回来的。后来换Playwright等页面渲染完再抓DOM,三行代码解决问题。从那以后我就觉得,Python爬虫这件事,选对工具比写对代码重要得多。工具不对,代码写得再好也抓不到数据。
Python爬虫三大主流方案的核心定位
| 1 | requests + BeautifulSoup:静态页面首选,代码量最少,爬豆瓣、新闻、博客这类服务端渲染的网站够用了 |
| 2 | Scrapy:大规模项目用,自带并发调度、去重、中间件、管道输出,一个命令生成项目骨架 |
| 3 | Playwright / Selenium:动态渲染页面专用,能模拟真人操作浏览器,绕过JS反爬和验证码检测 |
| 4 | 混合方案(Scrapy + Playwright):企业级项目的黄金组合,Scrapy负责调度和管道,Playwright负责渲染动态页面 |
一、requests + BeautifulSoup:5行代码爬完一个网页,静态页面的效率天花板
requests负责发送HTTP请求拿回HTML源码,BeautifulSoup负责解析HTML提取数据。这个组合是Python爬虫的入门标配,也是处理服务端渲染页面的最高效方案。什么叫服务端渲染?就是你右键查看网页源代码,能直接看到你要的数据——新闻正文、商品标题、博客文章,这些数据在HTML源码里就有,不需要等JS加载。
import requestsfrom bs4 import BeautifulSoup# 1. 发送请求,拿回网页源码url = "https://example.com/articles"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"}response = requests.get(url, headers=headers, timeout=10)# 2. 用BeautifulSoup解析HTMLsoup = BeautifulSoup(response.text, "html.parser")# 3. 提取数据:所有文章标题titles = soup.find_all("h2", class_="article-title")for t in titles:print(t.text.strip())# 4. 提取链接和作者for article in soup.select(".article-item"):title = article.find("h2").text.strip()link = article.find("a")["href"]author = article.find("span", class_="author").text.strip()print(f"{title} | {author} | {link}")上面这段代码就是最基础的爬虫骨架。requests.get发一个GET请求,headers里伪装成Chrome浏览器(User-Agent是最基础的伪装,不带的话服务器一看就知道是脚本),timeout设置超时防止卡死。BeautifulSoup拿到HTML后,用find_all按标签和class找元素,用select按CSS选择器提取,用text取纯文本,用['属性名']取属性值。
这个组合的优势在于极快的速度和极低的资源消耗。不需要启动浏览器,不需要渲染页面,一个请求从发送到返回可能只要0.2秒。爬一个100页的静态网站,用requests可能10秒跑完,用Playwright可能需要5分钟。劣势也很明显:遇到JS动态加载的数据、验证码、需要登录的页面,requests就抓瞎了。
怎么判断一个网站能不能用requests爬? 打开目标网页,右键"查看网页源代码"(不是F12检查元素),Ctrl+F搜索你要的数据关键词。能搜到 → requests能爬。搜不到 → 数据是JS动态加载的,需要上Playwright或直接抓API接口。
二、抓API比爬HTML高效十倍,浏览器F12里藏着一个不用解析DOM的数据源
很多新手有一个误区:以为爬虫就一定要解析HTML。实际上绝大多数动态网站的页面数据是通过API接口返回JSON的,直接抓接口比解析HTML快得多、稳得多、代码也简单得多。打开浏览器F12 → Network → XHR/Fetch标签,刷新页面,你会看到一串API请求。点开其中一个,Response里往往就是你要的数据,格式是规整的JSON。

import requestsimport json# 从F12 Network里找到的真实API地址api_url = "https://api.example.com/products/list"params = {"page": 1,"pageSize": 50,"categoryId": 100}headers = {"User-Agent": "Mozilla/5.0 ...","Referer": "https://example.com/products","X-Requested-With": "XMLHttpRequest"}# 直接请求API,返回的是JSON不是HTMLresp = requests.get(api_url, params=params, headers=headers)data = resp.json() # 直接转成Python字典# 提取商品列表for item in data["data"]["list"]:name = item["productName"]price = item["price"]sales = item["salesVolume"]print(f"{name} | 价格:{price} | 销量:{sales}")# 翻页继续抓total_pages = data["data"]["totalPages"]for page in range(2, total_pages + 1):params["page"] = pageresp = requests.get(api_url, params=params, headers=headers)# ... 继续处理抓API接口比解析HTML有三个明显优势:数据是结构化的JSON不需要BeautifulSoup解析;API返回速度远快于完整页面;反爬检测通常比HTML页面宽松。很多网站对HTML页面做了各种反爬限制,但API接口只做了简单的Referer和Header校验。唯一的难点是找到这个API地址——F12里XHR请求可能有几十条,需要花时间一个个看Response内容找到包含目标数据的那条。
一个省时间的技巧:在F12 Network面板的搜索框(左上角的放大镜图标)里输入数据关键词,比如某个商品名称或价格数字,Chrome会自动帮你定位到包含该数据的那个请求。不用一个一个点开看了。
三、Playwright:requests搞不定的JS渲染页面,用它就像开了个隐形浏览器
当你遇到页面数据全藏在JS里、右键查看源代码只能看到空壳、或者网站用了Cloudflare这类反爬网关的时候,Playwright就是解决问题的终极武器。它能启动一个真实的Chrome/Firefox/WebKit浏览器,完整渲染页面、执行所有JavaScript、等待动态内容加载完成后,再提取DOM里的数据。
from playwright.sync_api import sync_playwrightwith sync_playwright() as p:# 启动浏览器(headless=False可以看到界面)browser = p.chromium.launch(headless=False)page = browser.new_page()# 伪装浏览器指纹,降低被检测风险page.set_viewport_size({"width": 1920, "height": 1080})page.set_extra_http_headers({"Accept-Language": "zh-CN,zh;q=0.9"})# 访问目标页面page.goto("https://example.com/products", wait_until="networkidle")# 关键:等待目标元素出现后再提取数据page.wait_for_selector(".product-card", timeout=10000)# 提取所有商品信息cards = page.query_selector_all(".product-card")for card in cards:name = card.query_selector(".name").inner_text()price = card.query_selector(".price").inner_text()print(f"{name}: {price}")# 模拟翻页操作next_btn = page.query_selector(".pagination .next")if next_btn:next_btn.click()page.wait_for_timeout(2000) # 等新页面加载# 继续抓第二页数据...browser.close()Playwright比Selenium更受推荐的原因是速度更快、API更现代、自带等待机制。Selenium需要显式写WebDriverWait,Playwright的wait_for_selector和wait_until=networkidle已经内置了智能等待。而且Playwright默认不会被网站检测出是自动化工具(Selenium的webdriver属性很容易被检测)。
| 对比维度 | requests+BS4 | Scrapy | Playwright | Selenium |
|---|---|---|---|---|
| 上手难度 | 低 几行代码跑通 | 中高 需要理解框架 | 低 API简洁 | 中 需要配驱动 |
| 速度 | 极快 毫秒级 | 快 异步并发 | 慢 需渲染页面 | 最慢 启动开销大 |
| JS渲染 | 不支持 | 不支持(需中间件) | 完美 | 支持 |
| 反爬对抗 | 需手动加Headers/代理 | 中间件灵活配置 | 自带反检测,通过率高 | webdriver易被检测 |
| 适用规模 | 小型脚本,单站几百页 | 大型 百万级页面 | 中大型,需要渲染的站点 | 中小型,兼容旧项目 |
| 资源占用 | 极低 几MB内存 | 低 | 高 每页面几百MB | 高 |
四、六个最常见的反爬机制,以及对应的解决代码
写爬虫最花时间的不是提取数据,是跟各种反爬机制斗智斗勇。下面六个反爬场景覆盖了日常爬虫中90%以上的被拦截情况,每个都配了可直接用的代码。
反爬1:User-Agent检测
现象:返回403或空内容
解决:伪造请求头,随机切换UAheaders = {"User-Agent": random.choice(ua_list)}
反爬2:IP频率限制
现象:请求太快被暂时封IP
解决:加延时 + 代理IP池轮换time.sleep(random.uniform(1, 3))
反爬3:Referer/Origin校验
现象:API接口返回401/403
解决:headers加上来源页面地址"Referer": "https://目标站.com/page"
反爬4:Cookie/Session校验
现象:每次返回的都是未登录页面
解决:用requests.Session保持会话session = requests.Session()
反爬5:验证码
现象:弹出滑块/图形验证码
解决:降低频率 + 接打码平台API2captcha / 图鉴 等平台
反爬6:内容混淆/字体反爬
现象:网页看到的价格和源码里的数字不一样
解决:用Playwright截图后OCR,或分析字体文件映射paddleocr / tesseract
上面六个反爬里,前四个通过合理的代码策略基本都能解决,后两个(验证码和字体反爬)才是真正的硬骨头。遇到验证码不要硬刚——先检查是不是请求频率太高触发的,降低频率往往就能绕过去。如果降频后还弹验证码,再考虑接打码平台。字体反爬是最难搞的,如果目标站用了字体反爬且数据价值不高,建议直接放弃换其他数据源。

一个通用的稳健爬虫模板:requests.Session保持连接 + 随机UA + 1-3秒随机延时 + 3次重试机制 + 异常捕获。这套组合拳能应对大部分中等反爬强度的网站。代码量不超过30行,放在每个爬虫脚本的最前面,能减少80%的莫名报错。
五、数据存储:抓回来的数据放哪,三种方案从简单到专业
数据抓回来了,存哪是个实际的问题。不同场景适合不同的存储方案。
| 存储方案 | 适用场景 | 代码示例 |
|---|---|---|
| CSV文件 | 几千到几万条,临时分析 | pandas.DataFrame.to_csv("data.csv") |
| SQLite数据库 | 几十万条,需要查询去重 | conn = sqlite3.connect("data.db") |
| MySQL / PostgreSQL | 百万级以上,持续采集 | pymysql / psycopg2 + 批量INSERT |
新手最推荐的是CSV文件起步,pandas一行to_csv搞定。等数据量上去了,用SQLite无缝过渡——SQLite是单文件数据库,不需要安装配置,但支持完整的SQL查询和索引。等真正到了百万级数据、需要多进程并发写入的时候,再切MySQL或PostgreSQL。
六、踩过最多的三个坑,写爬虫前看清楚能省半天调试时间
以下三个坑是我和身边做爬虫的朋友反复踩过的,每次都是debug了半天才发现问题出在这种基础配置上。
坑1:编码问题导致中文乱码
requests默认用HTTP头里的编码,但很多中文网站的编码声明在HTML的meta标签里。解决方法:response.encoding = response.apparent_encoding
这句代码会根据实际内容自动检测编码,比手动指定靠谱。
坑2:没加timeout导致脚本卡死
requests.get(url)默认没有超时限制,遇到网络问题会一直等。必须加timeout参数:requests.get(url, timeout=10)
10秒没响应就抛异常,配合try/except捕获后重试。
坑3:用错了CSS选择器导致提取为空
BeautifulSoup的select和find_all经常因为class名有空格、动态class、嵌套层级不对而提取不到数据。调试技巧:先打印soup.prettify()看实际HTML结构,再用浏览器F12的"Copy selector"功能复制准确的CSS路径。
七、爬虫的法律边界:三条红线不能碰
2026年了,做爬虫如果不讲合规,轻则封IP,重则被起诉。三条硬红线必须记住:
红线一:不要爬取需要登录才能访问的非公开内容(用户个人信息、付费内容、内部系统数据),这涉嫌非法获取计算机信息系统数据罪。
红线二:不要以极高频率对目标网站发起请求,导致对方服务器瘫痪——这就从爬虫变成了DDoS攻击。
红线三:不要抓取受版权保护的内容用于商业用途。新闻、文章、图片、视频等内容的版权归原作者所有,商业使用需要获得授权。
底线原则:robots.txt里Disallow的目录不要爬;网站明确声明禁止爬取的内容不要碰;爬回来的数据用于内部分析和研究,不要直接倒卖或公开发布。
合规爬虫的最佳实践其实很简单:先看robots.txt,控制请求频率(1-3秒一次),只爬公开可访问的数据,不做商业转售。做到这四点,基本不会出问题。做竞品价格监控、舆情分析、学术研究这些正当用途,大部分网站的公开数据都是可以爬的。
回到最开始那个竞品价格监控的项目——后来用Playwright解决了JS渲染问题,加上API接口抓取,每天自动跑一遍,早上8点准时把Excel报表发到邮箱。整个过程不到200行Python代码。爬虫这件事说复杂也复杂,说简单也简单:静态页面用requests,动态页面找API或上Playwright,反爬对抗靠Headers、延时和代理三板斧,数据存CSV或SQLite。把这几样组合用熟了,市面上大多数网站的数据都能拿到手。
