欢迎光临
我们一直在努力

Python 爬虫避坑实战:从踩坑到健壮项目,新手别再复制粘贴跑代码

适合读者:有 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 怎么选?

很多新手纠结解析库选哪个,这里给出选型建议:

  • BeautifulSoup4:适合小页面,代码可读性高,调试简单,适合快速原型开发。
  • lxml 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% 场景,先把上面基础工程化做好。

  • JS 动态渲染页面
    • 优先抓 Ajax/fetch 接口,直接请求接口拿 JSON 数据,性能最优;
    • 接口加密难以逆向,使用 Playwright 无头浏览器模拟真实浏览器渲染页面。
  • IP 被封禁问题
    • 优先调整:降低并发,拉长随机休眠,不要直接上代理;
    • 确需大量采集,再考虑代理 IP 池,区分短效高匿代理、长效代理。
  • 数据存储
    • 少量数据:CSV/json 文件;
    • 大批量采集:MySQL、MongoDB 数据库,边爬边落库,防止内存溢出。
  • 异步 IO aiohttp:IO 密集场景追求极致速度,学习异步网络请求。
  • ⚠️爬虫合规提醒

  • 不要高频采集网站,尊重网站robots.txt协议;
  • 禁止爬取用户隐私、涉密数据;
  • 采集的数据仅限个人学习研究,禁止商用、倒卖。
  • 写在最后

    网上 demo 爬虫追求几行代码跑通效果,而真实项目追求健壮、容错、可维护。 学会复制粘贴一段爬虫代码很简单,但是写出网络抖动不会崩、遇到反爬有缓冲、可复用的爬虫脚本,才是核心能力。

    建议你写爬虫养成习惯:

  • 把网络请求逻辑单独封装;
  • 所有网络请求必须设置超时;
  • 做好异常捕获,不要让单个任务失败导致整个程序终止;
  • 控制并发,模拟人的访问节奏,不要暴力轰炸网站。
  • 赞(0)
    未经允许不得转载:171主机测评 » Python 爬虫避坑实战:从踩坑到健壮项目,新手别再复制粘贴跑代码
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址