适合读者:有 Python 基础,写爬虫总遇到403 封禁、乱码、程序崩溃、IP 被封的开发者,想写出稳定可复用爬虫脚本 环境版本:Python3.8+,Windows/Mac/Linux 全平台 预计阅读时间:45‑70 分钟
写在前面
网上绝大多数爬虫教程,只给一段可以临时跑通的 demo。复制代码运行,demo 页面正常,一换到真实网站就翻车:请求直接 403、网页中文乱码、程序遇到一个报错直接整体退出、短时间请求太多 IP 被拉黑。
很多新手陷入误区:以为会写requests.get()就算学会爬虫,忽略异常容错、反爬伪装、IO 稳定性、并发管控这些工程化能力。
爬虫真正难点不是 “拿到一次数据”,而是长时间、大批量、稳定不间断采集数据。 本文不从从零讲解 HTTP 基础,而是站在踩坑复盘视角,拆解新手高频错误,给出可复用模板,最后完成一个具备容错能力的图片下载实战项目。读完你会了解:
- 为什么 demo 可以跑,真实网站直接拦截;
- 如何写出 “不怕网络抖动” 的健壮请求函数;
- 并发爬虫的坑:不是线程开越多速度越快;
- 反爬伪装:UA、延时、Cookie 到底怎么合理使用,不要盲目堆砌;
- 实战项目:具备异常捕获、链接去重、限速、本地持久化的图片爬虫;
- 爬虫的边界:合规采集、爬虫法律注意事项。
一、新手写爬虫最容易踩的 7 个致命错误
错误 1:裸请求,不带任何 Headers
直接调用requests.get(url),缺少User‑Agent,服务器识别为程序爬虫直接返回 403。
❌错误写法
python
运行
import requests
url = "https://httpbin.org/get"
resp = requests.get(url)
print(resp.text)
✅最小可行改进:带上浏览器 UA 头
python
运行
import requests
HEADERS = {
"User‑Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36"
}
resp = requests.get("https://httpbin.org/get", headers=HEADERS, timeout=10)
print(resp.text)
错误 2:不设置 timeout,程序永久卡死
网络差、服务器响应慢的时候,请求会无限阻塞,脚本卡死不动,必须手动终止。
原则:所有 requests 请求,必须写 timeout 超时时间,建议设置 8‑15 秒。
错误 3:没有异常捕获,一次失败整个程序崩溃
网页 404、网络断开、服务器 500 报错,没有 try‑except 捕获异常,整个爬虫直接终止,已经爬取到的数据直接丢失。
错误 4:不处理编码,中文乱码一团糟
很多网页编码不是 UTF‑8,直接打印resp.text中文变成问号、乱码。
解决方案:使用resp.apparent_encoding自动识别网页编码。
错误 5:循环爬取不加休眠,疯狂请求直接封 IP
很多新手写死循环爬取,不做延时,一秒发起十几次请求,短时间触发网站流量防护,IP 封禁。
注意:不是休眠越长越好,使用随机延时,模拟人浏览网页的行为,固定延时也容易被识别。
错误 6:分不清静态 HTML 和 JS 动态渲染接口
拿到网页源码,搜索看不到目标数据,还在疯狂用 XPath 解析。本质:数据是 Ajax/fetch 异步接口返回,不在初始 HTML 源码。
排查小技巧:浏览器Ctrl+U查看网页源代码,搜索关键字,搜不到就是动态渲染数据。解决方案:抓接口或者使用 Playwright 无头浏览器。
错误 7:并发盲目开大线程数
看到多线程速度快,直接设置max_workers=50,大量并发同时请求,本机端口耗尽,同时网站直接封禁 IP。
爬虫属于 IO 密集任务,普通场景并发线程控制在 5‑15 之间足够。
二、构建可复用的安全请求工具函数
把请求逻辑封装成独立函数,项目所有地方统一调用,统一处理超时、异常、编码、请求头,不用每个爬虫脚本重复写重复代码。
safe_request.py工具模块
python
运行
import requests
import random
UA_POOL = [
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/124.0.0.0 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) Chrome/125.0.0.0 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:124.0) Gecko/20100101 Firefox/124.0"
]
def get_random_header(referer: str = None):
"""生成随机请求头"""
header = {
"User‑Agent": random.choice(UA_POOL)
}
if referer:
header["Referer"] = referer
return header
def safe_get(url: str, timeout: int = 10, referer: str = None):
"""
封装安全GET请求,捕获全部常见异常,自动处理编码
:param url: 请求地址
:param timeout: 超时时间
:param referer: 来源页
:return: Response对象,失败返回None
"""
headers = get_random_header(referer)
try:
resp = requests.get(url, headers=headers, timeout=timeout)
resp.encoding = resp.apparent_encoding
return resp
except requests.exceptions.Timeout:
print(f"[警告] 请求超时:{url}")
except requests.exceptions.ConnectionError:
print(f"[警告] 网络连接失败:{url}")
except requests.exceptions.TooManyRedirects:
print(f"[警告] 重定向次数过多:{url}")
except Exception as e:
print(f"[警告] 未知异常 {url}:{str(e)}")
return None
def safe_post(url: str, json_data: dict, timeout: int = 10, referer: str = None):
headers = get_random_header(referer)
try:
resp = requests.post(url, json=json_data, headers=headers, timeout=timeout)
resp.encoding = resp.apparent_encoding
return resp
except Exception as e:
print(f"[警告] POST请求异常 {url}: {e}")
return None
if __name__ == "__main__":
res = safe_get("https://httpbin.org/get")
if res:
print(res.text)
以后写爬虫,直接导入这个模块,所有网络请求统一调用safe_get(),规避 80% 低级错误。
三、页面解析:BS4 和 XPath 怎么选?
很多新手纠结解析库选哪个,这里给出选型建议:
安装依赖
bash
pip install requests beautifulsoup4 lxml
示例:调用上面封装好的safe_get,分别演示两种解析方式
python
运行
from bs4 import BeautifulSoup
from lxml import etree
from safe_request import safe_get
url = "https://httpbin.org/html"
resp = safe_get(url)
if not resp:
exit()
# BeautifulSoup解析
soup = BeautifulSoup(resp.text, "lxml")
title = soup.title.get_text(strip=True)
print(f"BS4解析标题:{title}")
# XPath解析
tree = etree.HTML(resp.text)
p_texts = tree.xpath("//p/text()")
for text in p_texts:
print(text.strip())
四、多线程并发:可控并发,拒绝疯狂开线程
重点提醒:多线程不能绕开网站反爬,只是提升 IO 场景下爬取速度,线程数越大不等于越快。
使用ThreadPoolExecutor线程池,控制最大并发数量,配合随机休眠,模拟真实访问节奏。
python
运行
import random
import time
from concurrent.futures import ThreadPoolExecutor
from safe_request import safe_get
def task(url):
# 每个任务内部做随机延时,降低请求频率
time.sleep(random.uniform(0.4, 1.2))
resp = safe_get(url)
if resp:
print(f"✅成功:{url} status:{resp.status_code}")
else:
print(f"❌失败:{url}")
if __name__ == "__main__":
url_list = [
"https://httpbin.org/get?p=1",
"https://httpbin.org/get?p=2",
"https://httpbin.org/get?p=3",
"https://httpbin.org/get?p=4"
]
# IO爬虫,max_workers设置5‑12区间,不要设置几十上百
with ThreadPoolExecutor(max_workers=5) as pool:
pool.map(task, url_list)
五、综合实战项目:具备工程容错的图片爬虫
项目特性:调用我们封装好的安全请求函数、图片链接去重、文件夹自动创建、并发可控、异常捕获、随机休眠限速。 不追求极致速度,优先保证稳定性。
image_spider.py
python
运行
import os
import random
import time
from lxml import etree
from concurrent.futures import ThreadPoolExecutor
from safe_request import safe_get
# =====================配置区,集中管理所有参数=====================
SAVE_FOLDER = "./download_images"
MAX_WORKERS = 6 # 最大并发线程
SLEEP_MIN = 0.3
SLEEP_MAX = 0.9
TARGET_URL = "https://httpbin.org/html"
# =================================================================
# 创建存储文件夹
if not os.path.exists(SAVE_FOLDER):
os.makedirs(SAVE_FOLDER)
def download_single_img(img_url):
"""下载单张图片"""
time.sleep(random.uniform(SLEEP_MIN, SLEEP_MAX))
resp = safe_get(img_url, timeout=12)
if resp is None or resp.status_code != 200:
print(f"图片下载失败 {img_url}")
return
filename = img_url.split("/")[-1]
save_path = os.path.join(SAVE_FOLDER, filename)
# 二进制写入保存图片
with open(save_path, "wb") as f:
f.write(resp.content)
print(f"✅图片已保存:{filename}")
def get_all_img_links(page_url):
"""解析页面提取全部图片链接,简单去重,过滤非http链接"""
resp = safe_get(page_url)
if resp is None:
return []
html_tree = etree.HTML(resp.text)
raw_links = html_tree.xpath("//img/@src")
valid_links = set()
for link in raw_links:
if link.startswith("http"):
valid_links.add(link)
return list(valid_links)
def main():
print(f"开始解析页面:{TARGET_URL}")
img_links = get_all_img_links(TARGET_URL)
print(f"共解析得到有效图片:{len(img_links)} 张")
if len(img_links) == 0:
print("未获取图片链接,程序退出")
return
# 并发下载
with ThreadPoolExecutor(max_workers=MAX_WORKERS) as pool:
pool.map(download_single_img, img_links)
print("====全部任务执行结束====")
if __name__ == "__main__":
main()
项目目录结构:
plaintext
├── safe_request.py # 通用请求工具类
├── image_spider.py # 图片爬虫主程序
└── download_images/ # 自动生成,存放下载图片
六、爬虫进阶路线,以及反爬应对正确思路
很多新手一上来就研究代理池、JS 逆向加密,其实 90% 场景,先把上面基础工程化做好。
- 优先抓 Ajax/fetch 接口,直接请求接口拿 JSON 数据,性能最优;
- 接口加密难以逆向,使用 Playwright 无头浏览器模拟真实浏览器渲染页面。
- 优先调整:降低并发,拉长随机休眠,不要直接上代理;
- 确需大量采集,再考虑代理 IP 池,区分短效高匿代理、长效代理。
- 少量数据:CSV/json 文件;
- 大批量采集:MySQL、MongoDB 数据库,边爬边落库,防止内存溢出。
⚠️爬虫合规提醒
写在最后
网上 demo 爬虫追求几行代码跑通效果,而真实项目追求健壮、容错、可维护。 学会复制粘贴一段爬虫代码很简单,但是写出网络抖动不会崩、遇到反爬有缓冲、可复用的爬虫脚本,才是核心能力。
建议你写爬虫养成习惯:



