新闻详情

新闻详情

首页 / 资讯中心 / 详情

Python爬虫请求库实战指南:从Session管理到代理与反爬应对

发布时间:2026/10/1 1:33:48来源:尧图网络
Python爬虫请求库实战指南:从Session管理到代理与反爬应对
先说结论如果你刚开始学爬虫或者写了几百行脚本但总被网站反爬搞得焦头烂额那么“请求库”这一关是绕不过去的。所谓爬虫请求库说白了就是帮你把“向服务器要数据”这件事做得又快又稳的工具包。Python 生态里最常用的就是 requests 和 httpx加上并发场景下的 aiohttp 与 httpx异步模式再往上才是 Scrapy 框架。很多人一上来就怼框架结果连 headers 怎么设置、Session 什么时候该用、重试策略怎么写都没搞清楚最后翻车了都不知道错在哪。我在实际项目里用请求库用了五六年从最简单的一行 requests.get 到后面自己封装请求模块踩过的坑不少。这篇就基于“爬虫请求库的使用”这个主题把我平时觉得最核心、最容易被忽略的细节全拆开讲一遍。适合刚入门 Python 爬虫的人也适合已经写了不少脚本但对请求层理解不透彻的朋友。1. 请求库到底解决什么问题为什么不能一上来就选框架1.1 从“拿数据”这个动作说起爬虫的逻辑本质上就三步构造请求、接收响应、解析数据。请求库解决的是前两步尤其是“构造请求”和“接收响应”过程中的各种细节。学过 HTTP 基础的人都知道一次请求包含 URL、请求方法、请求头headers、请求体body、Cookie、超时时间等而响应则包含状态码、响应头、响应体。如果没有请求库你得用 Python 自带的 urllib 去手动处理这些内容代码又长又容易出错。requests 这个库之所以流行就是因为它把 HTTP 的底层细节封装得足够“人话化”。一个最简单的例子import requests resp requests.get(https://example.com) print(resp.status_code) print(resp.text)三行代码就完成了请求和响应读取。而用 urllib 就得先构建 Request 对象再处理 urlopen还要考虑编码问题代码量直接翻倍。这不是说 urllib 不能用而是对于绝大多数爬虫场景requests 的简洁和可靠性更符合“快速迭代”的需求。1.2 直接用 Scrapy 行不行我的建议是“先别急”很多教程喜欢拿 Scrapy 当第一个例子我也理解因为框架功能全有调度器、有下载器、有中间件、有管道还能分布式部署。但问题是框架抽象了太多东西导致新手遇到请求层面报错时完全摸不着头脑。比如 Scrapy 里一个请求被反爬拒绝了你需要在 Downloader Middleware 里调 headers 或者代理如果不懂 requests 层面的原理根本不知道怎么改。我见过不少同事用 Scrapy 写爬虫出了问题只能到处搜“Scrapy 怎么设置代理”搜来搜去都是片段式答案最后也没理解为什么。反过来如果你先用 requests 把请求头、Cookie、Session、代理这些概念搞明白再去看 Scrapy 的 Request 对象和 middleware会发现就是一个封装版 requests很多坑都能自己排查。所以我的建议很简单入门阶段老老实实把 requests 用熟甚至可以直接用 requests 写完整的小项目。等你遇到性能瓶颈需要并发抓取大量数据时再引入 aiohttp 或 Scrapy才会游刃有余。1.3 请求库选型对照requests、httpx、aiohttp接触的请求库多了以后你会发现它们各有侧重点不是简单的“谁替代谁”的关系。这里先给出一份对照表后面再详细讲解各自的使用场景请求库同步/异步核心特点适用场景requests同步API 友好社区资料最丰富中小规模爬虫、调试、接口调用httpx同步/异步HTTP/2 支持异步接口与 requests 类似需要兼容同步与异步、追求性能aiohttp异步基于 asyncio高并发性能强大规模并发抓取urllib同步标准库无需安装临时脚本、环境受限时兜底Scrapy异步框架完整框架内置调度、去重、中间件大型爬虫项目、分布式爬虫对大多数刚开始学爬虫的人我建议先精通 requests 了解异步概念再按需补 httpx 和 aiohttp。没有必要一上来就非黑即白比如“异步才是王道”之类具体用到再学才是高效路径。2. 从 requests 基础用法说起那些文档不会写清楚的细节2.1 构造请求的“完整姿势”requests 的常用方法无非就是 get、post、put、delete 等但真正写爬虫时请求参数远不止 URL 和 headers 这么简单。我整理了一个比较完整的请求构造示例import requests session requests.Session() 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, Accept: text/html,application/xhtmlxml,application/xml;q0.9,image/webp,*/*;q0.8, Accept-Language: zh-CN,zh;q0.8,en-US;q0.5,en;q0.3, Referer: https://example.com/list, Origin: https://example.com } params { page: 1, size: 20, keyword: 爬虫 } cookies { sessionid: abcdef123456 } resp session.get( urlhttps://example.com/api/search, paramsparams, headersheaders, cookiescookies, timeout10, proxies{http: http://127.0.0.1:7890, https: http://127.0.0.1:7890}, verifyFalse )很多初学者只写 URL 和 headers遇到 403 或者被识别为机器人就开始怀疑人生。实际上网站判断你是不是真实浏览器看的远不止 User-Agent我还见过服务器校验 Accept、Accept-Language、Accept-Encoding 等等。特别是 Referer很多接口必须带上不然直接返回 403。另外要特别说明一个容易忽略的参数streamTrue。如果你要下载大文件不加这个参数会把整个文件加载进内存很容易撑爆内存。正确姿势是流式读取resp requests.get(url, streamTrue, timeout30) with open(large_file.zip, wb) as f: for chunk in resp.iter_content(chunk_size1024 * 1024): f.write(chunk)这种写法在做图片批量下载、爬取 PDF、视频文件时非常实用。2.2 状态码与异常处理别只盯着 200爬虫请求中最常见的错误就是把状态码当作唯一判据。真实的服务器响应是很复杂的200 不代表数据正确403/404 也不一定是网站坏了。我在实际工作中遇到过好几次接口返回 200但响应体里是一个登录跳转页面或者是 JSON 里的 code 字段不等于 0。所以判断请求是否成功关键在于响应体结构而不是 HTTP 状态码。合理的处理流程应该是先判断网络层是否异常超时、连接失败、DNS 解析失败再检查 HTTP 状态码但不是简单打印而是分类处理最后解析响应体看业务状态码决定是否重试或丢弃。代码示例from requests.exceptions import RequestException, Timeout, ConnectionError def fetch(url, **kwargs): try: resp requests.get(url, timeout10, **kwargs) except Timeout: print(f请求超时: {url}) return None except ConnectionError: print(f连接失败: {url}) return None except RequestException as e: print(f请求异常: {url}, 错误: {e}) return None if resp.status_code ! 200: print(fHTTP状态异常 {resp.status_code}: {url}) return None try: data resp.json() except ValueError: data resp.text if isinstance(data, dict) and data.get(code) not in (0, 200, 200): print(f业务状态异常: {data.get(code)}) return None return data这样分层处理后你的爬虫鲁棒性会好很多。我见过太多脚本一遇到超时就崩溃原因就是没有做异常捕获和分类。2.3 编码与 resp.text 的坑resp.text会根据响应头里的charset自动解码但碰到有些网站响应头不写 charset或者明明写了utf-8但内容却是 GBK 编码就会乱码。我踩过一次很深的坑某个网站返回的 Content-Type 是text/html没带 charsetrequests 默认用 ISO-8859-1 解码结果页面全是乱码。后来只能用resp.content拿到原始字节流再按 GBK 解码content resp.content.decode(gbk, errorsignore)更稳妥的方式是结合requests的apparent_encoding属性来猜编码resp.encoding resp.apparent_encoding text resp.text虽然apparent_encoding是靠 chardet 去猜速度慢一点但准确率不错。在写通用爬虫时我会写一个判断函数优先使用响应头编码响应头没有就尝试从 HTML 中 meta 标签找 charset实在找不到再采用 apparent_encoding。3. 进阶使用Session、Cookie、并发与重试的正确姿势3.1 Session 到底该不该用很多人的理解是错的requests.Session的好处是能保持 TCP 连接复用并在同一个会话中维持 Cookie这一点在做登录后抓取时特别关键。但很多人不管请求发多少次都新建一个 Session这其实没有充分利用它的连接池特性。正确做法是整个爬虫运行期间只需要一个 Session 实例所有请求都从同一个 Session 发出。举个例子你现在要登录某个网站然后访问个人中心。如果不使用 Session那么第二次请求需要手动把第一次返回的 Cookie 塞进 headers 或者 cookies 参数非常麻烦。而用 Session 之后requests 会自动保存服务端通过 Set-Cookie 设置的 Cookiesession requests.Session() # 第一次请求登录接口 login_resp session.post(https://example.com/login, data{username: test, password: 123456}) # 第二次请求个人中心自动携带 Cookie profile_resp session.get(https://example.com/profile)注意Session 不是线程安全的。如果开了多线程并发每个线程都要自己的 Session 实例否则会出现 Cookie 串线的问题。我之前在一个项目里图省事全局共用一个 Session结果同一个线程里登录了两个账号导致数据全串了排查了半天才发现是 Session 并发安全问题。3.2 处理 Cookie 的两条路线对于简单的会话维持用 Session 就够了。但有些场景需要你在外部存储 Cookie比如重启爬虫后免登录或者多个机器共享登录态。这时候就需要把 Cookie 转化为字典或者字符串保存起来。常见的做法是cookies session.cookies.get_dict() # 保存 cookies import json with open(cookies.json, w) as f: json.dump(cookies, f)下次爬虫启动时再加载with open(cookies.json, r) as f: cookies json.load(f) session requests.Session() session.cookies.update(cookies)这里面有一个坑get_dict()只能拿到不带 domain 和 path 的简单键值对如果网站使用了跨域 Cookie 或者带 HttpOnly 属性依然能正常获取但部分浏览器里的 SameSite 信息会丢失。大多数情况下够用了但如果遇到复杂登录态推荐用session.cookies这个 RequestsCookieJar 对象直接序列化再配合 pickle 保存能保留更多属性。import pickle with open(cookies.pkl, wb) as f: pickle.dump(session.cookies, f)这里要注意 pickle 有版本兼容性问题跨 Python 版本读取可能会失败所以生产环境我更建议存成字典格式虽然信息少一点但通用性强。3.3 超时、重试与指数退避让你看起来像“正常人”很多新手写爬虫requests.get 里不带 timeout默认就会一直等下去直到网线断开才报错。正确做法是每个请求都要设置 timeout一是防止线程长期挂起二是避免给服务器造成压力。timeout 实际上可以是一个元组(connect_timeout, read_timeout)分别代表建立连接和读取数据的超时时间。一般来说 connect 可以设置 5 秒read 设置 10 到 15 秒。重试策略同样重要。网络抖动、临时封禁、服务端 5xx都会导致请求失败。盲目重试只会给服务器添乱还可能让自己 IP 被封。合理的重试机制应该加入“指数退避”第一次重试等 1 秒第二次等 2 秒第三次等 4 秒最多重试三到五次。这样既给服务器恢复时间也让自己的请求模式更像真实用户。用 requests 自带适配器也能实现自动重试但比较简单。更灵活的是自己写装饰器import time from functools import wraps def retry(max_retries3, delay1, backoff2): def decorator(func): wraps(func) def wrapper(*args, **kwargs): current_delay delay for attempt in range(max_retries): try: return func(*args, **kwargs) except requests.RequestException as e: if attempt max_retries - 1: raise print(f请求异常第 {attempt 1} 次重试{current_delay} 秒后重试: {e}) time.sleep(current_delay) current_delay * backoff return wrapper return decorator使用起来很简单retry(max_retries3, delay1) def fetch_page(url): resp requests.get(url, timeout5) resp.raise_for_status() return resp.text注意403 这种“可预期”的状态码不应该一上来就疯狂重试很可能你已经被风控了。这时先去检查 headers、代理、Cookie而不是靠重试去“撞运气”。3.4 并发设计多线程、多进程还是异步“爬虫并发设计到底哪个好”这个问法本身就是个伪命题。没有银弹不同场景有不同的最优解。如果你是爬 API 接口瓶颈通常在网络 IO所以多线程或者 asyncio 异步都能提升效率如果你是爬取静态页面并做大量 CPU 解析比如 XML 解析、图片压缩那么多进程才能利用多核 CPU。我在实际项目中常用的方案有三种对于中小型爬虫直接用concurrent.futures.ThreadPoolExecutor就够了简单、易理解、好维护。每个线程维护自己的 Session配合限速器控制请求频率。from concurrent.futures import ThreadPoolExecutor, as_completed def crawl_one(url): session requests.Session() # 带上请求头 headers {...} resp session.get(url, headersheaders, timeout10) return resp.text urls [https://example.com/page/1, https://example.com/page/2] with ThreadPoolExecutor(max_workers5) as executor: future_to_url {executor.submit(crawl_one, url): url for url in urls} for future in as_completed(future_to_url): result future.result() # 处理 result对于需要高并发的场景aiohttp 是更轻量的选择。但 aiohttp 写起来没有 requests 直观需要熟悉 asyncio 的写法。下面给一个最小示例import asyncio import aiohttp async def fetch(session, url): async with session.get(url, timeout10) as resp: return await resp.text() async def main(): urls [https://example.com/page/1, https://example.com/page/2] async with aiohttp.ClientSession() as session: tasks [fetch(session, url) for url in urls] results await asyncio.gather(*tasks) for text in results: # 处理 text pass asyncio.run(main())如果你用的是 httpx 的异步模式写法几乎和 requests 一模一样迁移成本很低。但不管用哪种并发方式都需要控制速率。我见过太多人开 100 个线程去怼某个小网站结果 IP 被秒封反而把自己搞得很被动。限速器的实现很简单import time import threading class RateLimiter: def __init__(self, max_calls, period1.0): self.max_calls max_calls self.period period self.calls [] self.lock threading.Lock() def __call__(self): with self.lock: now time.time() # 移除超出时间窗口的记录 self.calls [t for t in self.calls if now - t self.period] if len(self.calls) self.max_calls: sleep_time self.period - (now - self.calls[0]) if sleep_time 0: time.sleep(sleep_time) self.calls.append(time.time())界面友好一点还可以用time.sleep(random.uniform(0.5, 1.5))这种随机等待模拟人工操作节奏。不论设计多复杂记住一个原则高并发是你的权利但别滥用。4. 反爬与应对策略请求库使用中的“临场课”4.1 请求头伪装不只是 User-Agent谈到反爬大多数人第一反应是换 UA。但现在稍微正规一点的网站都会做浏览器指纹检测光换 UA 很容易露馅。我在实际抓取过程中会尽量把关键请求头补全尤其是以下这些User-Agent标识浏览器环境Accept告诉服务器客户端能接收什么类型Accept-Language语言偏好建议设成 zh-CN,zh;q0.9Accept-Encodinggzip, deflate, brrequests 会默认解压 gzip但 br 需要特别支持Referer页面来源很多网站会校验Origin常用于 POST 请求校验Connection: keep-aliveSession 默认就会带上一个推荐的模拟浏览器的 headers 配置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, Accept: text/html,application/xhtmlxml,application/xml;q0.9,image/avif,image/webp,*/*;q0.8, Accept-Language: zh-CN,zh;q0.9,en;q0.8, Accept-Encoding: gzip, deflate, br, Referer: https://example.com, DNT: 1, Upgrade-Insecure-Requests: 1, }需要注意的是headers 不是越全越好有些网站会通过请求头组合判断异常。比如一个普通浏览器发起的请求几乎不会同时带上Origin和Referer只有当从某个页面跳转过来时才会带。所以做 POST 接口时才需要额外加 Origin。4.2 代理 IP 的正确打开方式热词里反复出现“python 爬虫ip代理”说明开发者对代理的需求非常旺盛。很多网站有 IP 访问频率限制如果只用单一 IP 去抓很容易触发风控。代理的用法并不复杂在 requests 里可以通过 proxies 参数传入proxies { http: http://user:password127.0.0.1:7890, https: http://user:password127.0.0.1:7890, } resp requests.get(url, proxiesproxies, timeout10)但代理有两大坑。第一个坑是全站代理与局部代理的问题通过 Session 设置代理后在后续所有请求中都会生效包括那些本来不需要代理的内网接口容易白白消耗流量第二个坑是代理失效问题很多免费代理根本不可用半分钟前测通半分钟后超时。所以我建议你封装一个代理管理器每次请求前做一次代理可用性检查或者采取“先用直连失败再换代理”的策略。更推荐的做法是使用商用代理池按需拿 IP而不是自己维护一大堆免费 IP。但我要强调一点代理不是万能的。很多网站的风控不只是看 IP 维度还会结合设备指纹、Cookie、JS 行为验证。如果你光是换 IP 但其他特征都没改照样会被识别。4.3 verify 与证书验证的坑提到 https 请求requests 默认会对 SSL 证书做验证。有些公司内网或自建系统用了自签名证书直接请求会报requests.exceptions.SSLError。此时可以设置verifyFalse同时需要忽略警告import urllib3 urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) resp requests.get(url, verifyFalse)不过verifyFalse会带来安全隐患容易被中间人攻击。如果你用的是公司内部的测试环境问题不大如果要长期跑生产任务建议还是把证书链配置完整或者用verify/path/to/ca.pem指定自定义 CA。同时verifyFalse还会导致requests在每次请求时打印警告日志忽略掉就行不用觉得奇怪。另外在设置代理时如果代理本身也带 MITM 抓包比如 fiddler、Charles会出现提示证书错误的情况这时候也要临时把 verify 关掉但注意安全风险。4.4 遇到登录限制怎么办很多网站的核心数据需要登录才能看这时请求库要模拟登录流程。最常见的登录方式有两种基于 Cookie 的会话登录和基于 Token 的接口鉴权。基于 Cookie 的登录很简单用 Session 去 POST 用户名密码然后保持会话。关键在于登录接口的字段名、加密参数、验证码处理。有些网站前端会对密码进行 RSA 或 AES 加密你直接用明文 POST 是拿不到正确响应的。这时你需要找到加密逻辑一般是在 JS 中用 Python 模拟同样的加密过程。纯 JS 加密非常复杂的场景我甚至会考虑用 Playwright 先运行一遍再提取 Cookie而不是死磕加密算法。这里就引出了大家常说的“playwright 相比于直接解码爬虫的优点”遇到动态渲染和复杂加密时浏览器自动化确实能省很多事。基于 Token 的接口一般会在登录后返回一个 access_token后续请求放在 Authorization 头里resp requests.post(https://example.com/api/login, json{username: u, password: p}) token resp.json()[access_token] headers { Authorization: fBearer {token} } resp requests.get(https://example.com/api/data, headersheaders)这里要特别注意 Token 的过期时间最好在爬虫里做一次登录态检测发现 401 后重新登录避免任务跑到一半全部失败。5. 从请求库到完整爬虫做一个可复用的请求模块5.1 为什么要把请求模块单独抽象我见过很多爬虫项目每个函数里都写一遍 requests.getheaders 也重复复制粘贴。一旦需要统一改超时时间、统一加代理、统一做日志改到你崩溃。好的做法是单独抽一个requester.py模块把请求逻辑、会话管理、重试和代理全部封装起来业务层只需要传 URL 和解析器。这里给一个简化版的设计思路# requester.py import requests import logging from requests.exceptions import RequestException logger logging.getLogger(requester) class Requester: def __init__(self, headersNone, timeout10, proxiesNone, retry_times3): self.session requests.Session() self.session.headers.update(headers or {}) self.timeout timeout self.proxies proxies self.retry_times retry_times def get(self, url, paramsNone, **kwargs): for attempt in range(self.retry_times): try: resp self.session.get( url, paramsparams, timeoutself.timeout, proxiesself.proxies, **kwargs ) resp.raise_for_status() return resp except RequestException as e: logger.warning(f请求失败: {url}, 第 {attempt 1} 次重试, 错误: {e}) time.sleep(1 * (2 ** attempt)) return None def post(self, url, dataNone, jsonNone, **kwargs): # 类似实现 pass这样封装后业务代码里只需要from requester import Requester requester Requester( headers{...}, timeout5, retry_times3 ) resp requester.get(https://example.com/api/data, params{page: 1}) html resp.text看起来很简单但收益巨大。全项目统一入口后续加日志、加限速、加代理轮换都只改这一个模块。5.2 日志与监控别等爬虫挂了才想起请求库的调用量一大日志就很重要了。我习惯在每个请求开始和结束时记录 URL、状态码、耗时、重试次数。用 Python 的 logging 模块在模块里设置好格式输出到文件和控制台。import logging logging.basicConfig( levellogging.INFO, format%(asctime)s [%(levelname)s] %(name)s: %(message)s, handlers[ logging.FileHandler(crawler.log, encodingutf-8), logging.StreamHandler() ] )这样在请求封装模块里直接logger.info(开始请求: %s, url)如果请求异常就logger.error。跑一段时间后你分析日志就能知道哪个 URL 频繁超时、哪些代理不稳定方便针对性调整。监控这块更进阶的做法是把关键指标推到 Grafana 或者直接发告警到企业微信/钉钉但这是项目后期的事。前期建议先把日志格式规划好字段尽量完整时间、URL、状态码、耗时、重试次数、错误信息。以后排查问题会轻松很多。5.3 动态渲染页面怎么处理题目带到了热词“playwright 相比于直接解码爬虫的优点”我借这个点聊一下请求库的边界。requests 这类请求库拿到的是服务器返回的 HTML 或接口 JSON但很多现代网站的数据是通过 JavaScript 动态加载的直接在响应体里搜不到。处理动态渲染有几种思路第一种是用接口逆向打开浏览器开发者工具找到 XHR 请求把接口 URL 和参数抠出来直接用 requests 模拟。这种效率最高但前提是接口参数不复杂也没有加密签名。第二种是用 Playwright 或 Selenium 这类浏览器自动化工具让浏览器自己执行 JS然后你只需要获取渲染后的页面内容。优点是不用分析复杂的请求参数缺点是资源消耗大、速度慢、并发能力弱。第三种是两者的结合用 Playwright 拿到首次加载后的 Cookie 或者 token然后继续用 requests 去请求后续接口。这种策略能兼顾速度与稳定性我在实际项目中经常这么干。在开发阶段我更推荐先用 Playwright 的交互式调试模式去确认页面结构等摸清接口规律后再切回 requests 做数据抓取。这样既能少写很多无用的请求代码也能避免被复杂的反爬机制折磨。5.4 一个实际的抓取案例从请求到落库为了把前面所有内容串起来我写一个完整的抓取示例。假设目标是一个图书列表页数据在 HTML 中需要翻页抓取然后保存到 CSV。import csv import time import requests from bs4 import BeautifulSoup HEADERS { User-Agent: Mozilla/5.0 ..., Accept-Language: zh-CN,zh;q0.9 } def fetch_page(session, page_num): url fhttps://example.com/books?page{page_num} resp session.get(url, headersHEADERS, timeout10) resp.encoding utf-8 return resp.text def parse_page(html): soup BeautifulSoup(html, html.parser) items soup.select(.book-item) data [] for item in items: title item.select_one(.title).text.strip() price item.select_one(.price).text.strip() data.append({title: title, price: price}) return data def main(): session requests.Session() all_data [] for page in range(1, 6): html fetch_page(session, page) data parse_page(html) all_data.extend(data) print(f第 {page} 页完成共 {len(data)} 条数据) time.sleep(1) with open(books.csv, w, newline, encodingutf-8-sig) as f: writer csv.DictWriter(f, fieldnames[title, price]) writer.writeheader() writer.writerows(all_data) if __name__ __main__: main()这个例子虽然简单但涵盖了请求、解析、分页、限速、存储几个核心模块。实际项目里你可能还需要加代理、加异常重试、加日志但骨架已经完整了。6. 常见问题与排查技巧实录6.1 请求超时但浏览器正常这是最让人崩溃的问题之一。浏览器能打开页面但 requests 一请求就超时。原因可能有两种一是你的本地网络环境对 Python 发出的请求做了限制二是目标网站检测到了非浏览器特征故意延迟或丢弃。排查步骤我通常这样走第一步先换一个简单请求测试比如不设任何 headers 请求百度如果能成功说明基本网络没问题如果失败检查代理设置。第二步加上完整 headers模拟浏览器访问目标页面。第三步如果还是超时把 timeout 时间调大再试同时检查是不是需要带 Cookie。第四步用requests.utils.dump_header查看一下服务器返回的响应头看看有没有Retry-After之类的提示。有时候超时是因为服务器对连接数做了限制比如只允许同一 IP 建立有限个连接并发太高就会出问题。这时候降低并发数或者用代理分散 IP 反而能解决。6.2 拿到 403/418 怎么判断是头部校验还是行为校验403 常见的反爬响应。如果返回的内容是验证码页面说明你被 JS 挑战拦截了比如 Akamai、Cloudflare 等防护。如果返回的是一个自定义 JSON 或 HTML且内容里明确写着“请求过快”或“访问异常”那多半是频率限制。418 是另一个经典的状态码很多网站用它标记“你是茶壶”Im a teapot以嘲弄爬虫。如果看到 418基本可以确定是你没有通过浏览器特征校验。此时把 headers 补全、禁用不正常的 HTTP 版本或者尝试换浏览器场景比如加上Upgrade-Insecure-Requests等等。我遇到过最恶心的一种是请求 10 次前 9 次正常第 10 次突然 403然后过 5 分钟又能访问了。这就是典型的频率风控没有太多技术含量只需要把你的请求频率调低用代理池轮换 IP并且加上随机 sleep 即可。6.3 请求返回的数据与浏览器看到的完全不一样这种情况经常发生在使用了响应式布局或者动态渲染的网站上。你通过 requests 拿到的 HTML 可能只是空壳真实内容都在 XHR 接口里。此时用 requests 直接请求页面没什么意义清空思路去分析接口。打开开发者工具刷新页面筛选 XHR 或 Fetch找到返回数据的那个接口然后用同样的 URL 和参数去请求。注意接口可能携带时间戳、签名等动态参数需要进一步处理。还有一种可能请求头的Accept-Encoding设置不当导致服务器返回了预压缩的响应requests 虽然会自动解压 gzip但对于 brBrotli格式支持不够完善建议直接不要禁用 br或者安装brotli库增强支持。遇到乱码时也多考虑编码和压缩问题。6.4 代理 IP 为什么老是失效免费代理是爬虫新手最容易踩的坑。今天测通明天就失效甚至请求还没发出去就超时。应对办法只有一个自己维护一个动态代理池定时检测代理可用性把失效的剔除把新的加进来。如果你不想自己造轮子可以直接购买商用代理 API按次或按时购买稳定性有保障。但无论哪种方式核心概念相同在使用代理前先测试代理失效要自动重试切换而不是傻傻地让整个任务失败。另外HTTP 代理和 HTTPS 代理要区分清楚。有些代理只支持 HTTP 流量用于 HTTPS 请求时会报ConnectionError这时需要换用支持 CONNECT 方法的代理。在 requests 的 proxies 字典里http 和 https 要分别配置不能只写一个。6.5 常用排查速查表我把日常排查问题整理成一张速查表方便大家照着做现象可能原因排查方向超时代理失效/网络不稳/目标慢先用直连测试加大 timeout检查代理403Headers 不完整/IP 被限补全请求头更换代理降低频率418被 JS 挑战或识别为机器人补全浏览器特征使用 Playwright 预取 Cookie乱码编码识别错误手动 decode设置 resp.encoding数据缺失动态渲染/接口鉴权分析 XHR先获取 token 再请求内存爆了未使用 stream 流式下载设置 streamTrue 并迭代读取登录失效Cookie 过期/token 过期重新登录定时刷新登录态证书错误verify 未设置或代理干扰配置正确证书或临时 verifyFalse这张表并不能覆盖所有问题但能帮你快速定位 90% 的请求层故障。如果遇到表里没列出的情况建议先从三个方面考虑网络通不通、请求头全不全、目标网站是不是改了策略。写在最后请求库这东西说简单也简单说深也深。简单的是 API 调用一上午就能学会深的是底层原理和面对真实网站的应对策略需要一次次踩坑才能积累。我个人在实际操作中的体会是永远不要把一个请求库看成一个“发请求工具”它是你和目标网站之间交互的入口。从请求头的构造、会话的管理、重试的策略到并发的控制每一步都藏着决策逻辑。搞懂这些再复杂的技术栈Scrapy、分布式、异步都只是工具壳子真正有核心竞争力的还是你对 HTTP 与反爬博弈的理解深度。最后再分享一个小技巧当你怀疑某个请求是不是被服务器特殊对待时先用curl模拟一下同样参数再用 requests 对比很多问题立刻就能定位出是库的问题还是自己代码的问题。这个习惯救过我无数次希望也能帮到你。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

笔记本安装打印机驱动全攻略:从佳能到HP P1106的故障排查 2026/10/1 6:18:05

笔记本安装打印机驱动全攻略:从佳能到HP P1106的故障排查

新买的佳能打印机搬到笔记本前,插上USB线,系统提示“设备安装失败”,或者装完驱动后点打印,任务队列里转两圈直接报错——这种场景我见过太多次了。很多人第一反应是驱动有问题,重新下载了好几遍,折腾一晚上…

阅读更多 →
微信小游戏大家来找茬带流量主源码:休闲益智变现闭环搭建指南 2026/10/1 6:18:05

微信小游戏大家来找茬带流量主源码:休闲益智变现闭环搭建指南

简介:微信小程序益智游戏“大家来找茬/找不同”完整源码包,集成流量主广告功能,定位为可直接学习与二次开发的微信小游戏项目。适合小程序开发者、游戏编程初学者,以及希望快速搭建找茬类游戏或研究小游戏流量变现方式的运营者。压…

阅读更多 →
TensorFlow实战指南:从环境安装到手写数字识别,对比PyTorch选型 2026/10/1 6:18:05

TensorFlow实战指南:从环境安装到手写数字识别,对比PyTorch选型

有人问我,2024年学深度学习到底选TensorFlow还是选PyTorch。我一般先不急着站队,而是反问他:你学完框架之后准备做什么?是想快速验证论文里的想法,还是要把模型做成一个真正可以对外服务的产品?这两个目标对…

阅读更多 →
OnlyOffice配置HTTPS全指南:从证书申请到Nginx反向代理实战 2026/10/1 6:18:05

OnlyOffice配置HTTPS全指南:从证书申请到Nginx反向代理实战

1. 项目概述与环境准备做企业内部文档协作的时候,OnlyOffice是个绕不开的名字。它最大的价值在于:一套开源的在线文档套件,能直接在浏览器里编辑Word、Excel、PPT,还支持多人协同,效果和体验跟桌面Office很接近。但我发…

阅读更多 →
电脑微信聊天记录与文件迁移备份恢复指南 2026/10/1 6:17:58

电脑微信聊天记录与文件迁移备份恢复指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
OpenHarmony 五种截屏方式与避坑指南 2026/10/1 6:17:51

OpenHarmony 五种截屏方式与避坑指南

hdc shell snapshot_display这个命令,我第一次在 OpenHarmony 开发板上敲的时候,返回了一句 usage,当时还以为工具没编进去。后来翻了图形子系统的源码才发现,参数写法和我想的不一样。这件事让我意识到,OpenHarmony 的…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉