直接把最常被问到的三件事放在前面:第一,爬虫不是黑魔法,它只是用代码模拟浏览器去访问网页、拿回内容、再按需提取;第二,Requests负责“要数据”,BeautifulSoup负责“拆数据”,这两个库加在一起,能覆盖日常八成以上的抓取需求;第三,入门真正的门槛不是语法,而是对网页结构、请求头、频率控制这些细节的理解。这篇就用一个完整的小项目,把从零到能跑的流程串一遍。
如果你是刚接触Python、想搞懂网络数据怎么来的新手,或者已经写过几段脚本但总在解析和反爬环节卡壳,这篇应该能帮上忙。我会把每一步的原理、参数选择、以及实测中踩过的坑都交代清楚。
1. 爬虫核心思路与方案选型
1.1 爬虫到底在爬什么:理解HTTP请求与响应的关系
爬虫的本质可以压缩成一句话:**模拟浏览器发起HTTP请求,接收服务器返回的HTML文档,然后从文档中提取你要的信息。**这里的关键在于“模拟浏览器”这五个字。服务器并不关心你是谁,它只认请求头里的User-Agent、Cookie等字段是否像一个真实的浏览器。这就是为什么很多新手用代码请求网站会收到403或429,而用浏览器打开却一切正常的原因——服务器识别出你是“非人类访问”。
所以入门爬虫,第一课不是写代码,而是建立一个认识:你写出的每一个请求,都相当于在服务器门口递了一张访问单。请求头里的User-Agent告诉对方“我是什么设备”,Referer告诉对方“我从哪个页面跳过来的”,Cookie告诉对方“我之前来过没有”。这三样信息不全,服务器就有理由怀疑你是机器人。后文会具体演示怎样在Requests里设置这些字段。
1.2 为什么入门首选Requests与BeautifulSoup,而不是Scrapy或Selenium
很多人一上来就纠结选什么框架,我的建议是:**入门阶段根本不需要框架,用Requests加BeautifulSoup这两个库就够了。**原因很简单,Scrapy是个完整的爬虫框架,它有调度器、下载器、管道、中间件,功能强大但学习曲线陡峭,你还没搞懂怎么提取数据,就得先搞懂它的目录结构和回调机制。Selenium则是个浏览器自动化工具,它确实能绕过大部分JavaScript渲染的网站,但它启动一个真实浏览器,速度慢、内存占用高,并不适合入门阶段理解爬虫的运行逻辑。
Requests和BeautifulSoup是两条“积木”:
- Requests负责网络请求,它把HTTP的GET、POST、HEAD这些操作封装成一行代码,处理超时、重试、会话保持都相对直观。
- BeautifulSoup负责HTML解析,它把服务器返回的页面文本变成一棵可遍历的节点树,你可以像查字典一样定位到任何一个标签。
用这两个库写出的代码虽然看起来“土”,但每一步都清清楚楚,出了问题能立刻定位到是请求环节还是解析环节。等你理解了整个链路,再上手Scrapy或者Selenium就顺理成章了。
1.3 这个方案的最小技术栈与适用场景
用Requests和BeautifulSoup组合的典型技术栈如下:
| 层次 | 组件 | 作用 |
|---|---|---|
| 请求层 | Requests | 发送HTTP请求,维持会话,处理超时与重试 |
| 解析层 | BeautifulSoup4 | 解析HTML/XML文档,定位目标数据 |
| 辅助层 | lxml | BeautifulSoup的解析器引擎,比Python内置的html.parser解析速度快得多 |
| 存储层 | 内置csv/json模块 | 把提取到的数据写入文件,方便后续分析 |
这个组合最适合的领域是静态网页或者数据直接嵌入在HTML里的页面,比如新闻列表、商品信息、文章正文、表格数据等。如果目标网页数据是通过Ajax异步加载的,或者由JavaScript动态渲染出来的,Requests直接请求页面源码是拿不到的,这时需要抓包找到真实的接口地址,再对着接口发请求。这个思路在后面的实战环节也会提到。
2. 环境准备与工具链搭建
2.1 Python版本与虚拟环境的坑,你大概率会碰到
我见过太多新手在环境问题上浪费大量时间,所以这部分专门写一节。先说Python版本,Python 3.8、3.9、3.10、3.11在当前阶段都能正常运行Requests和BeautifulSoup,但新项目建议直接用Python 3.10以上版本,原因有两个:一是类型注解和语法特性更完善,二是官方及第三方库对旧版本的支持正在逐步缩紧。
然后是虚拟环境。不要图省事直接pip install装到全局环境里,项目一多依赖就会打架。A项目需要requests 2.28,B项目需要requests 2.31,装在全局就会出现一个装完另一个崩的情况。每个项目建独立虚拟环境是Python开发的底线操作,这一步在Windows、macOS、Linux下都一样。
2.2 安装Requests和BeautifulSoup的两种方式
最直接的方式是命令行安装,推荐使用阿里云或清华的镜像源,速度会快很多:
# Windows下,确保已激活虚拟环境 pip install requests beautifulsoup4 lxml -i https://pypi.tuna.tsinghua.edu.cn/simple # macOS/Linux下,同样先激活虚拟环境 pip3 install requests beautifulsoup4 lxml -i https://pypi.tuna.tsinghua.edu.cn/simple需要注意,beautifulsoup4在导入时写的却是from bs4 import BeautifulSoup,这个“包名和导入名不一致”的问题时常让新手摸不着头脑。另外lxml是解析器,安装它不是因为BeautifulSoup不内置解析器,而是它自带的html.parser在解析复杂页面时速度偏慢,换成lxml后体验会好很多。
2.3 安装验证与第一个最小请求
装好依赖后,用下面这段代码验证环境是否正常:
import requests from bs4 import BeautifulSoup response = requests.get("https://httpbin.org/get") print(response.status_code) print(response.text[:200])如果你能看到状态码200和一段JSON格式的返回内容,说明网络请求链路已经打通了。httpbin.org是一个在线测试工具,专门用来测试HTTP请求的各种参数,非常适合入门阶段练手。拿到这个基础后再往页面解析的方向走。
3. Requests库核心实操:从发送请求到处理响应
3.1 GET请求入门与响应内容解析
GET请求是爬虫最常用的方式,它的语义是“向服务器索取资源”。用Requests发起GET请求只需一行代码:
import requests url = "https://httpbin.org/get" response = requests.get(url) print(response.status_code) # 状态码:200表示成功,404表示页面不存在,403表示禁止访问 print(response.headers) # 响应头:包含Content-Type、Set-Cookie等信息 print(response.text) # 响应体的文本形式,页面源码就是这个这里有几个参数你需要一开始就养成习惯:
timeout:设置请求超时时间,建议设成(3, 5)这样的元组形式,第一个值是连接超时,第二个值是读取超时。headers:自定义请求头,至少要把User-Agent换上浏览器的值。params:传递URL查询参数,比如翻页时的?page=2,用字典形式传参比手动拼URL更清晰。
没有设置超时时间的请求是有隐患的。目标服务器如果响应慢,你的爬虫会一直傻等,程序看起来像死了一样。设置合理的超时时间后,配合异常处理,能做到“请求失败就跳过”,保证整个爬取流程不会因为一个坏链接而中断。
3.2 请求头伪装与常见403问题
很多网站对爬虫的第一道防线就是检查User-Agent。默认情况下Requests发出去的User-Agent是python-requests/x.x.x,这个标识服务器一眼就能识别出是爬虫。伪装方法很简单:
import requests headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8", "Referer": "https://www.google.com/" } response = requests.get("https://example.com", headers=headers, timeout=(3, 5))如果你的请求返回403,优先检查是不是User-Agent没有配置好。另外要留意有些网站会检查Accept和Accept-Language字段,所以把常用的几个请求头都加上,效果会更稳定。这里有个可以临时救急的小技巧:Chrome浏览器按F12打开开发者工具,切到Network面板,随便点开一个请求,右键复制为cURL,然后用requests.utils里的工具转成Python代码。这样拿到的请求头是最接近真实浏览器的。
3.3 POST请求与表单数据的提交方式
有些页面需要提交表单才能获取数据,这时要用POST请求。Requests的POST方法和GET几乎一样:
import requests url = "https://httpbin.org/post" form_data = { "username": "test_user", "password": "123456" } headers = { "User-Agent": "Mozilla/5.0 ..." } response = requests.post(url, data=form_data, headers=headers, timeout=(3, 5)) print(response.json())这里的data参数接收的是表单格式的数据,如果目标接口要求JSON格式,需要改用json=form_data这种传参方式,同时Requests会自动把请求头的Content-Type设置成application/json。这个细节非常关键,很多接口调不通就是因为你传的格式和服务器期望的不一致。
需要注意,搜狗搜索、百度搜索等查询类接口虽然看起来是GET请求,但部分特殊接口也会要求POST提交关键词参数。你在写爬虫之前,先打开开发者工具把实际请求方式看清楚,再决定用哪种方法。
3.4 Session会话维持:爬取需要登录或Cookie的页面
Session是Requests里非常实用的对象。它像一个“浏览器容器”,能把多次请求之间的Cookie自动保存并携带。适合的场景是那些需要先登录再访问数据的站点。
import requests session = requests.Session() session.headers.update({ "User-Agent": "Mozilla/5.0 ..." }) # 先请求登录页面,模拟登录 login_url = "https://httpbin.org/post" login_data = {"username": "test", "password": "123456"} session.post(login_url, data=login_data) # 再访问需要登录信息才能访问的页面 profile_url = "https://httpbin.org/cookies" response = session.get(profile_url) print(response.text)用Session管理Cookie能免去手动从响应头里提取Set-Cookie再拼到请求头的麻烦。当你踩到某些验证码或者高仿浏览器校验的网站时,可以把浏览器里的Cookie手动粘到Session的请求头里,临时应急是完全可行的。
4. BeautifulSoup解析与数据提取:从HTML到干净数据
4.1 BeautifulSoup的解析器选择:html.parser还是lxml
BeautifulSoup本身只是个“壳”,它依赖底层的解析器把HTML字符串转成节点树。官方内置的是html.parser,轻量、无需安装,缺点是容错性和解析速度都一般。lxml是用C语言实现的,解析速度快、容错能力强,遇到不太规范的页面也能尽量还原结构。
用的时候,在BeautifulSoup构造函数的第二个参数传入解析器名称即可:
from bs4 import BeautifulSoup html_doc = """ <html> <head><title>测试页面</title></head> <body> <div class="content"> <h1>文章标题</h1> <p class="intro">这是简介</p> <a href="https://example.com/1">链接1</a> <a href="https://example.com/2">链接2</a> </div> </body> </html> """ soup = BeautifulSoup(html_doc, "lxml")对比一下,html.parser在Python 3.10以下版本对某些不闭合标签的页面处理得很吃力,而lxml基本能抗住大部分真实网页的混乱结构。所以我的建议很明确:别用内置解析器,直接上lxml。
4.2 find、find_all与CSS选择器:三种定位方式怎么选
解析HTML最核心的操作是“定位”,BeautifulSoup提供了三种常用的方式:
find(name, attrs):找到第一个符合条件的节点,返回一个Tag对象。find_all(name, attrs):找到所有符合条件的节点,返回一个列表。select(css_selector):用CSS选择器语法定位节点,返回列表。
实际使用中,find_all和select出镜率最高。举几个组合示例:
# 找到所有class为item的div标签 items = soup.find_all("div", class_="item") # 用CSS选择器找到所有id为main-content下的a标签 links = soup.select("#main-content a") # 找到所有包含data-id属性的节点 nodes = soup.select("[data-id]") # 找到所有h2标签,并提取文本 for h2 in soup.find_all("h2"): print(h2.get_text(strip=True))需要特别提醒的是class是Python的关键字,所以在find_all里指定class属性时必须写成class_,带一个下划线。如果不小心写成了class,会直接报语法错误。
4.3 提取文本与属性的常用方法
拿到Tag对象后,最常做的操作是提取文本和获取属性值:
# 获取标签内的纯文本内容 text = tag.get_text(strip=True) # 获取标签的属性值 link = tag.get("href") img_src = tag.get("data-src") # 获取标签名 name = tag.nameget_text(strip=True)能自动去掉首尾空白字符和换行符,这一招几乎每次都用得上。另一个关键点是部分网站会把图片真正的地址放在><div class="post-item"> <h2 class="post-title"><a href="/p/123">这是一篇文章</a></h2> <span class="post-date">2024-01-01</span> </div>
正确解析思路:
post_list = soup.select(".post-item") # 第一步:定位所有文章容器 for post in post_list: title_tag = post.select_one(".post-title a") # 在容器内查找标题链接 date_tag = post.select_one(".post-date") # 在容器内查找日期 title = title_tag.get_text(strip=True) link = title_tag.get("href") date = date_tag.get_text(strip=True)不要直接对整个soup做select找所有标题、所有日期,再把它们按索引拼起来。这种方式在页面结构有缺失时会导致数据错位,索引对不上就是一场灾难。容器内查找的方式既清晰又健壮,推荐作为默认策略。
5. 完整Demo:爬取一个静态网页列表并存储到CSV
5.1 Demo页面选定与解析目标
这里我们用https://quotes.toscrape.com/来做演示,这是一个专门为爬虫初学者设计的网站,没有复杂的反爬机制,页面结构也干净,非常适合检验Requests和BeautifulSoup的组合能力。
我们要抓的数据是页面里的名人名言、作者和标签链接。先打开页面,用浏览器F12观察一下HTML结构(如果你不打开开发者工具,建议先去打开,否则下面的选择器你会看不懂来源)。
页面中每条引言的HTML结构类似:
<div class="quote"> <span class="text">“人生苦短,我用Python。”</span> <span> <small class="author">某个作者</small> </span> <div class="tags"> <a class="tag" href="/tag/python/">python</a> <a class="tag" href="/tag/code/">code</a> </div> </div>5.2 完整代码与逐行解释
这个Demo我从头到尾写一遍,并把每个关键点的作用注释清楚,你可以直接参考复现。
import csv import requests from bs4 import BeautifulSoup headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36" } def fetch_page(url): """发送GET请求并返回BeautifulSoup对象""" try: response = requests.get(url, headers=headers, timeout=(5, 10)) response.raise_for_status() # 状态码不是200时抛出异常 response.encoding = response.apparent_encoding # 自动猜测网页编码 return BeautifulSoup(response.text, "lxml") except requests.RequestException as e: print(f"请求失败:{e}") return None def parse_quotes(soup): """从页面源码中提取名言、作者和标签""" quotes = [] for quote in soup.select(".quote"): text = quote.select_one(".text").get_text(strip=True) author = quote.select_one(".author").get_text(strip=True) tags = [tag.get_text(strip=True) for tag in quote.select(".tag")] quotes.append({ "text": text, "author": author, "tags": ", ".join(tags) }) return quotes def save_to_csv(quotes, filename="quotes.csv"): """将数据写入CSV文件""" with open(filename, "w", newline="", encoding="utf-8-sig") as f: writer = csv.DictWriter(f, fieldnames=["text", "author", "tags"]) writer.writeheader() writer.writerows(quotes) print(f"数据已保存到 {filename},共 {len(quotes)} 条") def main(): all_quotes = [] for page in range(1, 5): # 爬取前4页,页码1到4 url = f"https://quotes.toscrape.com/page/{page}/" print(f"正在爬取第 {page} 页...") soup = fetch_page(url) if soup is None: continue all_quotes.extend(parse_quotes(soup)) save_to_csv(all_quotes) if __name__ == "__main__": main()这段代码里有几个值得展开说明的细节:
response.encoding = response.apparent_encoding这行是为了处理网页编码问题。有些网站在响应头里不声明编码导致中文乱码,用apparent_encoding让Requests从页面内容里自动猜编码。csv.DictWriter配合fieldnames参数能把字典列表干净地写入CSV,newline=""是防止在Windows上写CSV时出现空行。encoding="utf-8-sig"是给Excel用户准备的,如果不带BOM头,Excel打开CSV会乱码。
5.3 分页逻辑与URL规律分析
分页是爬虫里最常见的逻辑之一。这个Demo网站上,page/1、page/2、page/3这样的URL规律非常规整,所以直接用一个for循环拼接URL即可。但真实世界的分页有两种典型情况:
- 查询参数翻页:
?page=2这种形式,用requests.get(url, params={"page": page})即可。 - JavaScript异步加载翻页:这种页面直接改页码无用,需要抓包找到接口地址,通常返回JSON数据而不是HTML。
遇到第二种情况,用Requests直接请求接口URL,拿到JSON再解析即可。接口的请求参数往往需要从Network面板里逐字段分析,耐心对照即可。
5.4 编码问题:中文乱码与编码检测的实战处理
中文站点的编码问题几乎是必经之路。老旧的网站可能用gbk或gb2312编码,新网站大多用utf-8,但响应头不声明的也很多。apparent_encoding用的底层库是chardet或charset_normalizer,它能从HTML内容本身推断出编码。实测下来,对大部分中文页面这个推断是靠谱的,但偶尔也有误判的时候。
如果自动判断的编码还是乱码,可以在拿到response.content原始字节数据后手动尝试几种常见编码:
for encoding in ["utf-8", "gbk", "gb2312", "big5"]: try: text = response.content.decode(encoding) break except UnicodeDecodeError: continue这个办法虽然笨,但在应急场景下很有效。
6. 常见请求限制与应对方法
6.1 429状态码:你被限流了,不是被封锁了
很多新手在爬虫运行一段时间后突然遇到429 Too Many Requests,第一反应是自己被网站封了IP,其实不尽然。429明确表示“你在单位时间内发出了太多请求”,服务器没有禁止你访问,只是临时降速。这种情况常见于没有控制请求频率的爬虫,比如一个for循环连续请求200个页面,每页间隔不到0.5秒。
应对429最直接的手段是主动控制请求间隔:
import time import random for page in range(1, 10): # 每次请求后随机休眠2到4秒,模拟人工浏览节奏 time.sleep(random.uniform(2, 4)) ...随机间隔比固定间隔效果更好,因为固定间隔也容易被识别为机器行为。除了控制频率外,还可以在请求时观察响应头里的Retry-After字段,服务器有时会明确告诉你“多少秒后再访问”,这时就严格按它说的来。
6.2 超时重试与requests.adapters.Retry策略
请求超时也是家常便饭。网络波动、服务器过载或单页响应缓慢都可能触发超时。最简单的处理是用try-except捕获并跳过:
import requests from requests.adapters import HTTPAdapter session = requests.Session() adapter = HTTPAdapter(max_retries=3) # 最多重试3次 session.mount("http://", adapter) session.mount("https://", adapter) try: response = session.get(url, headers=headers, timeout=(5, 10)) response.raise_for_status() except requests.exceptions.RequestException as e: print(f"请求异常:{e}")HTTPAdapter里设置的max_retries是在请求失败后的重试次数,但需要留意,它重试时也可能触发429。
另外要注意区分超时类型:ConnectTimeout是建立连接时超时,ReadTimeout是服务器响应数据时超时。在复杂场景下,可以把这两类异常分开捕获,分别处理。
6.3 IP被封与代理使用的边界问题
如果爬取量大到一定程度,即使控制了频率,IP仍可能被临时封禁。这时行业内常规做法是使用代理IP池轮换出口IP。Requests的代理设置很简单:
proxies = { "http": "http://your-proxy-ip:port", "https": "http://your-proxy-ip:port" } response = requests.get(url, headers=headers, proxies=proxies, timeout=(5, 10))关于代理,需要特别说明合规底线:**不要用别人未公开的代理地址,不要将爬虫用于恶意攻击或非法获取非公开数据,也不要抓取明确声明禁止爬虫抓取的页面。**爬虫必须在robots协议和网站服务条款允许的范围内运行。入门阶段完全可以靠“慢速爬取”和“合理间隔”来避免IP问题,不需要一上来就上代理。
6.4 登录态与Cookie失效导致的401/403
有的网站部分数据需要登录才能查看。你明明已经用过Session登录,但请求一段时间后又出现403或者401(未认证),这通常是因为Cookie过期了。常见原因是Session间隔太久没有请求,服务器端主动失效了会话。
遇到这种情况,要么重新发起登录流程,要么检查响应头里的Set-Cookie和页面里的隐藏表单字段(如CSRF Token)是否发生了变化。某些网站会在每个表单里附带一个动态token,下次请求时必须带着这个新token,否则就拒绝访问。这是很多爬虫莫名其妙失效的隐藏原因,排查时要优先看请求参数里是不是有每次都变化的字段。
7. 从静态页面到动态页面的必要过渡
7.1 如何判断一个页面是不是动态渲染的
拿到一个网页,先右键查看源代码,看看目标数据是不是直接写在HTML里。如果在源代码里能看到完整的数据内容,这个页面就是静态页面,Requests直接抓就没问题;如果源代码里只有一堆Javascript代码和空壳div,数据在浏览器里能看到但源码里没有,那基本可以确认是动态渲染的页面。
抓这种动态页面有两条路:
- 打开开发者工具,Network面板筛选XHR或Fetch请求,找到返回JSON数据的接口,直接请求接口地址。
- 用Selenium或Playwright这类浏览器自动化工具渲染页面后抓取。
优先走第一条路。接口方式速度快、资源占用少,还不需要装额外的浏览器驱动。很多看似复杂的网站,找到背后的API后,爬虫反而比静态页面更简单,因为JSON数据比HTML好解析得多。
7.2 逆向工具链的价值:Browser DevTools与cURL转换
调试爬虫时一把好用的工具链能省大量时间。我常用的流程是:
- F12打开开发者工具,选定网络请求面板。
- 刷新页面,找到包含目标数据的请求。
- 右键请求,复制为cURL格式。
- 用cURL转Python代码的工具(在线版或本地脚本均可)一键生成Requests代码,保证请求头、Cookie、POST数据都不丢失。
用这个流程处理复杂接口时,基本能保证“请求层面”不会出错。后续的问题排查就集中在数据解析和反爬逻辑上。
7.3 从Requests到异步框架的演进路径
当爬取规模从几十页增长到几千页,Requests的同步阻塞模式会成为瓶颈。线程池、进程池是初级优化手段,concurrent.futures.ThreadPoolExecutor就能简单实现并发下载:
from concurrent.futures import ThreadPoolExecutor, as_completed urls = [f"https://quotes.toscrape.com/page/{i}/" for i in range(1, 11)] def fetch_and_parse(url): soup = fetch_page(url) return parse_quotes(soup) with ThreadPoolExecutor(max_workers=5) as executor: futures = [executor.submit(fetch_and_parse, url) for url in urls] for future in as_completed(futures): ...但并发的代价是需要更精细地控制频率和错误处理,否则很容易触发429甚至封IP。异步框架如aiohttp、httpx的AsyncClient是更高阶的选择,但初学者不要过早陷入并发优化里,把单机、单线程的爬虫跑通、跑稳,再谈性能。
8. 反爬机制与合规意识:越早明白越好
8.1 常见反爬手段与Forbidden响应的应对
目前常见的反爬机制大致有几类:
- 请求头校验:针对User-Agent、Referer字段做检查,应付这类问题用“请求头全套伪装”即可。
- 频率检测:单位时间内统计请求次数,超出阈值就限流或封IP。解决办法是降低频率、增加随机延时。
- 行为分析:分析鼠标轨迹、点击行为、访问路径等,Requests模拟不了真实的浏览器行为,遇到这类校验只能考虑浏览器自动化。
- 动态Token验证:每次请求都带一个由Javascript计算出来的签名参数,单纯的Requests很难绕过。
对初学者而言,遇到以上机制时不要急着研究对抗技术,先判断这个网站值不值得爬。如果目标网站明确不欢迎爬虫,加上验证码、滑块等高强度校验,直接放弃通常是最理性的选择。
8.2 robots协议与网站服务条款的基本边界
robots协议是网站与爬虫之间的“君子协定”,存放在网站根目录下的robots.txt文件中,例如https://example.com/robots.txt。你可以在浏览器中直接打开查看,它会清楚列出哪些路径允许爬取、哪些禁止爬取。
入门阶段一定要养成先看robots协议的习惯。虽然它不具备法律强制力,但尊重它是这个领域的基本职业素养。同时,网站的Terms of Service中关于数据抓取的条款也需要留意,尤其是涉及用户个人数据、版权内容时。
8.3 爬虫数据使用的三条红线
结合多年实操经验,我给自己定过三条数据使用红线,分享出来供参考:
- 不爬取个人隐私数据:手机号、身份证号、家庭住址、私人聊天记录等,不管技术上多容易拿到都不碰。
- 不商业化使用有版权的内容:新闻文章、付费课程、图片素材等,除非获得授权,否则不用于任何商业用途。
- 不给目标服务器造成压力:控制请求频率、控制并发数,保持“善意爬虫”的姿态。
守住这三条,大多数正常的学习和研究需求都能在安全合规的范围内完成。入门阶段的目标是掌握技术,不是真的去大规模制造流量,所以克制一点反而能走得更远。
8.4 学习爬虫的正确进阶路径参考
如果你看完这篇,想继续深入爬虫方向,我建议按这样的顺序进阶:
- 第一步:熟练掌握Requests的各种使用场景,包括Session、代理、超时重试、上传下载文件等。
- 第二步:系统学习HTTP协议知识,理解状态码、请求头、缓存、Cookie的完整语义。
- 第三步:掌握正则表达式,作为BeautifulSoup的补充工具。
- 第四步:了解常见反爬机制的原理与应对策略。
- 第五步:学习Scrapy框架,理解爬虫的工程化组织方式。
- 第六步:接触分布式爬虫、消息队列、增量抓取等内容,为生产级爬虫做准备。
每一步都不需要追求精通再进入下一步,够用就可以往前走,遇到问题再回来补齐。
9. 实操心得的几点补充
最后再分享几个我在实际项目里用Requests和BeautifulSoup时攒下来的细节经验,算是给前面内容的补充:
第一,不要把response.text直接打印出来调试。大型页面打印出来只会刷屏,什么都看不清楚。先用len(response.text)看长度,再切片前几百个字符确认内容即可。
第二,写解析代码时先用一段HTML存起来本地调试。把抓到的页面保存成html文件,用BeautifulSoup反复调试解析逻辑,不用每次修改代码都重新请求服务器,效率高也不容易被封。
with open("page.html", "w", encoding="utf-8") as f: f.write(response.text) # 后续调试直接读取本地文件 with open("page.html", "r", encoding="utf-8") as f: soup = BeautifulSoup(f.read(), "lxml")第三,每个选择器都值得先单独验证一遍。写select或find_all的时候,先把选中的结果个数打出来,确认不是0再继续往下写。如果选出来是空列表,说明选择器写错了或页面结构和你预想的不同,这时候再研究选择器要比写完整个爬虫再回来排查快得多。
第四,注意数据清洗的时机。拿到文本后立刻做strip和空白字符压缩,不要等到写入文件时才处理,这时数据可能已经在列表里乱成一团了。
我自己踩过最深的一个坑是:写爬虫时一切正常,换到另一台机器上却各种超时和403,最后发现是那台机器的网络出口被目标网站限制过,和代码本身没有任何关系。所以遇到问题时,先确认环境原因再怀疑代码,排查效率会高很多。