前言
从今天开始,我会持续更新爬虫系列内容,覆盖从基础爬虫到 JS 逆向,再到 App 逆向的完整学习路径。
中间也会穿插讲解一些爬虫必须具备的前置知识,比如:
- HTTP 协议基础
- 浏览器开发者工具的使用
- 页面结构分析方法
每一节都会配合实际案例进行讲解。需要提前说明的是,由于网站结构是动态变化的,所以示例代码可能在未来某些时间点失效。但这并不影响核心方法------只要掌握分析思路,爬虫本质是万变不离其宗的。
如果在学习过程中遇到问题,也欢迎随时交流讨论。
一、什么是爬虫?
爬虫(Web Crawler)本质上是一种自动化脚本程序,用于批量抓取互联网上已公开的数据。
这里需要明确一个常见误区:
爬虫只能获取"本来就能看到的数据",而不是突破权限获取隐藏数据。
如果试图获取服务器内部未公开的数据,那已经属于安全攻击或渗透测试范畴,是完全不同的领域。
二、为什么需要爬虫?
很多人会疑惑:既然数据网页上都能看到,为什么不直接手动复制?
原因很简单:效率差异。
- 人工复制:一个页面可能需要数秒甚至更久
- 程序爬取:可以做到毫秒级批量获取
除此之外,爬虫的优势还包括:
- 批量处理大规模数据
- 自动清洗与结构化存储
- 可与数据分析流程结合
- 支持定时任务与持续监控
本质上,爬虫解决的是数据获取自动化问题。
三、可抓取的数据是否有价值?
答案是:非常有价值。
只要是公开数据,并且能够被合理利用,就具备分析意义,例如:
- 电商平台商品价格监控
- 价格波动趋势分析
- 竞品数据对比
- 行业信息采集与建模
以电商场景为例,如果每天定时抓取商品价格,就可以构建时间序列数据,从而分析价格变化规律,甚至用于商业决策。
四、本系列学习目标
本系列将逐步带你完成以下内容:
- requests 库基础使用
- XPath / CSS 选择器解析页面
- HTTP 请求与响应机制
- 浏览器开发者工具抓包分析
- JS 逆向基础
- App 逆向基础入门
最终目标是让你具备独立分析网页结构并完成数据采集的能力。
今天我们主要讲requets库,并完成几个网站案例。
五、爬虫的流程
- 找到存放数据的url。
- 分析该url的请求方式(get还是post),及参数设置(需要携带哪些参数)
- requests库进行抓取
- 返回的响应进行提取(提取的方法可以通过xpath、bs4、re)处理,得到干净的数据并存储
六、requests库的使用
requests的安装和Python环境
在正式开始爬虫之前,我们首先需要安装 requests 库。
打开命令行(CMD、PowerShell 或终端)输入:
pip install requests
如果电脑中安装了多个 Python 环境,也可以指定某个环境安装:
python -m pip install requests
# 或者
python3 -m pip install requests
安装完成后,可以进入 Python 解释器测试:
import requests
print(requests.__version__)
如果没有报错,则说明安装成功。
需要特别注意的是:
在命令行中使用
pip install时,默认安装到当前命令行对应的 Python 环境中。
而很多同学在使用 PyCharm 编写代码时,经常会遇到:
ModuleNotFoundError: No module named 'requests'
明明已经安装了 requests,却依然提示找不到模块。
出现这种情况,通常是因为:
- 命令行使用的是 Python 环境 A;
- PyCharm 项目使用的是 Python 环境 B。
两个环境并不是同一个,因此会导致库无法使用。
在 PyCharm 中可以通过:
File -> Settings -> Project -> Python Interpreter
查看当前项目所使用的解释器,并切换到已经安装了 requests 的 Python 环境。

抓包流程
接下来以这个网站为案例,演示如何分析网站数据来源。
2019中国票房
进入网站后,按下:
F12
打开浏览器开发者工具。
本教程使用的是 Edge 浏览器进行演示,Chrome 浏览器的界面基本一致,其它浏览器可能会存在一定差异,但核心功能都是类似的。
开发者工具中,我们重点关注的是:
Network(网络)
面板。
很多人所说的**「抓包」**,本质上就是查看浏览器与服务器之间的网络通信过程,而这些通信记录都会显示在 Network 面板中。
因此,在分析网站数据时,我们通常都会切换到该面板。

url补充了解
这里做一点对url的补充,这能让我们对整个请求流程有了解,方便后续分析:
当我们在浏览器中输入一个网址(URL)并按下回车时,本质上就是在向服务器请求某个资源。
这里的**「资源」**可以是:
- 一个网页;
- 一张图片;
- 一段视频;
- 一个 JSON 数据接口;
- 一个文件;
- 等等。
简单来说:
URL 就是网络世界中资源的地址。
我们可以把访问网站的过程类比成去餐馆吃饭。
如果想吃一道菜,首先需要知道餐馆在哪里,然后再告诉服务员自己想吃什么。
整个过程大致如下:
找到餐馆 -> 告诉服务员需要什么 -> 服务员返回菜品
对应到网络世界中:
知道资源地址(URL) -> 向服务器发起请求 -> 服务器返回数据
例如:
http://www.boxofficecn.com/boxoffice2019
这个 URL 可以简单拆分为:
http:// 协议
www.boxofficecn.com 网站所在的服务器地址
/boxoffice2019 具体资源路径
其中:
www.boxofficecn.com
表示我们要访问的是哪个网站。
而:
/boxoffice2019
则表示我们希望获取该网站中的哪一个具体资源。
因此,当我们在浏览器中输入 URL 时,其实就是在告诉浏览器:
请帮我去这个网站中获取这个地址对应的资源。
浏览器收到我们的指令后,会自动按照网络协议向服务器发送请求。服务器接收到请求后,会根据 URL 中提供的信息找到对应资源,并将数据以响应(Response)的形式返回给浏览器。
浏览器再对这些数据进行解析和渲染,最终展示成我们看到的网页内容。
必须勾选的两个选项
进入 Network 面板后,建议勾选以下两个选项:
Preserve log
Disable cache
这两个选项对于爬虫分析非常重要。

1. Preserve log(保留日志)
勾选后,即使页面发生跳转、刷新或者重定向,之前的网络请求记录也不会被清空。
这样可以保留完整的请求链路,方便后续分析。
否则,页面一跳转,之前的请求记录可能就消失了。
2. Disable cache(禁用缓存)
浏览器为了提升访问速度,会将部分资源缓存到本地。
当再次访问相同页面时,浏览器可能直接从本地缓存读取数据,而不会真正向服务器发送请求。
如果没有禁用缓存,我们在 Network 面板中可能看不到真正的数据请求,从而影响抓包分析。
因此,在进行爬虫分析时,通常建议勾选:
Disable cache
需要注意的是:
只有开发者工具保持打开状态时,该选项才会生效。
刷新页面查看请求
如果是在网页已经加载完成之后才打开开发者工具,此时 Network 面板通常是空的。如下图:

这是因为页面加载过程中产生的请求已经结束了。
因此,我们需要:
- 保持开发者工具打开;
- 按下
F5或点击刷新按钮重新加载页面。
刷新之后,就可以看到大量的网络请求。

如何快速定位数据来源
面对成百上千个请求,一个最常见的问题就是:
网页上的数据究竟来自哪一个请求?
最简单的方法就是:
搜索关键词。
在 Network 面板中:
Ctrl + F
或者点击左上角的放大镜图标,即可打开搜索框。

接着,可以选择网页上的某个文本内容作为关键词进行搜索。
例如:
四个春天
或者:
春天
一般建议:
- 尽量选择网页中具有代表性的文本;
- 优先使用较短的关键词;
- 尽量避免包含特殊符号。
因为网页源码中的内容,并不一定与页面上看到的内容完全一致。
例如页面显示:
作者
但源码中可能是:
<b>作</b><b>者</b>
或者:
作<span>者</span>
此时直接搜索:
作者
可能搜索不到结果。
因此,当搜索失败时,可以尝试:
- 缩短关键词;
- 分开搜索;
- 搜索部分内容。
这是抓包分析过程中非常常见的一种情况。
搜索:
四个春天
后,发现只有一个匹配结果。
双击搜索结果,即可定位到对应请求。

随后切换到:
Response(响应)
面板查看返回的数据。

如果发现:
- 数据内容与网页展示内容一致;
- 数据结构中包含页面中的所有信息;
那么基本可以确定:
该请求就是网页数据真正的来源。
分析请求信息
找到目标请求后,需要进一步分析请求的组成。
切换到:
Headers(请求头)
面板:

重点观察以下内容:
请求地址
Request URL
即:
url
请求方式
常见请求方式有:
GET
POST
PUT
DELETE
爬虫中最常见的是:
GET
POST
请求参数
观察:
Query String Parameters
或者:
Form Data
Payload
查看请求是否携带参数。
例如:
?page=1
&limit=20
如果请求依赖这些参数,我们后续使用 requests 时也必须传递相同参数。
代码实现
完成抓包分析后,我们就可以利用 requests 模拟浏览器发送请求。
python
import requests
url = 'http://www.boxofficecn.com/boxoffice2019'
resp = requests.get(url=url)
print(resp.text)
运行后即可获取服务器返回的数据。

响应对象(Response)
requests.get() 返回的是一个:
Response
响应对象。
其中包含了服务器返回的全部信息。
常用属性如下:
resp.status_code # 状态码
resp.headers # 响应头
resp.text # 字符串形式的数据
resp.content # 字节形式的数据
resp.json() # 将JSON自动转为Python对象
例如:
print(resp.status_code)
print(resp.text)
如果状态码为:
200
说明请求成功。
text 与 content 的区别
对于网页文本:
print(resp.text)
通常使用:
.text
因为 requests 会自动进行字符编码处理。
而对于:
- 图片
- 音频
- 视频
等二进制文件,则通常使用:
resp.content
因为这些文件本质上都是字节数据,不需要进行字符解码。
例如下载图片:
python
img = requests.get(img_url)
with open('1.jpg', 'wb') as f:
f.write(img.content)
运行后发现,返回的数据与网页中的内容完全一致。
说明我们已经成功获取到了网页的真实数据。
不过,这只能说明我们比较幸运。
该网站几乎没有做任何反爬措施。
而在实际开发过程中,大多数网站都会进行一定程度的反爬,例如:
- 校验请求头(User-Agent、Referer 等)
- 校验 Cookie
- 校验 Token
- 请求频率限制
- IP 限制
- 签名加密
- 验证码
- JavaScript 动态生成参数
因此,在后续章节中,我们还需要学习如何模拟浏览器行为,以及如何绕过各种反爬机制。
什么是反爬?
简单来说,网站为了识别、限制甚至阻止爬虫程序而采取的一系列措施,就称为反爬虫(Anti-Spider)机制。
既然网站会反爬,那么一个很自然的问题就是:
服务器是如何知道请求来自正常用户,还是来自爬虫程序的呢?
我们知道,爬虫程序本质上也是在模拟用户访问网站。而普通用户通常是通过浏览器或者 App 来访问网站的。既然爬虫和浏览器最终都会向服务器发送请求,那么服务器又是如何区分它们的呢?
实际上,这里面包含了两个值得深入思考的问题:
- 浏览器和 App 为什么能够访问网站?它们访问网站的底层原理是什么?
- 服务器又是如何判断一个请求来自正常的浏览器或 App,而不是来自爬虫程序的?
很多初学者可能从来没有思考过第一个问题。
因为在我们的认知中:
浏览器就是用来上网的。
这个现象太过于理所当然,以至于我们很少去思考它背后的原理。
但如果仔细想想,就会发现一个有趣的问题:
为什么在浏览器地址栏中输入:
https://www.baidu.com
就能够打开网页,而在记事本、计算器或者其他普通软件中输入同样的网址,却无法访问网站呢?
原因其实很简单:
浏览器内部实现了一整套网络通信协议(例如 TCP/IP、HTTP、HTTPS 等),能够按照这些协议规定的格式与服务器进行通信。
换句话说,浏览器并不是"天生就会访问网站",而是因为浏览器的开发者已经帮我们实现好了这一整套通信流程。
当我们在浏览器地址栏中输入一个网址并按下回车时,浏览器实际上会自动完成大量工作,例如:
- 解析 URL;
- 通过 DNS 查询域名对应的 IP 地址;
- 与服务器建立 TCP 连接;
- 如果是 HTTPS,还需要进行 TLS 握手;
- 按照 HTTP 协议构造请求报文并发送给服务器;
- 接收服务器返回的数据;
- 解析 HTML、CSS、JavaScript;
- 最终将网页渲染并展示给用户。
当然,上述这套流程大家了解即可,并不需要死记硬背。在爬虫学习阶段,我们更关注的是:
- 请求是如何发送出去的;
- 服务器返回的数据是如何接收的;
- 如何分析并模拟浏览器发送请求。
而爬虫程序所做的事情,本质上就是:
使用代码去模拟浏览器发送网络请求,并获取服务器返回的数据。
既然服务器期望接收到的是由浏览器发送的请求,那么爬虫程序就必须尽可能让自己发送的请求看起来像是浏览器发送的请求,否则就很容易被服务器识别并触发反爬机制。
不过需要注意的是,现代网站的反爬远不止请求头这么简单。修改请求头只能解决最基础的一类反爬问题,后续我们还会接触 Cookie、Token、加密参数、JS 逆向等更加复杂的反爬机制。
请求头与反爬
最常见的就是通过请求头来识别你是不是爬虫,我们可以继续以刚刚的电影网站的爬取代码为案例,查看一下通过requests发送请求默认的header,和浏览器的有何不同:
这是我们爬虫程序的,user-agent直接就是Pythonxxx,这不是相当于直接告诉对方我就是爬虫程序吗?我们再来看看浏览器的:

浏览器发送的请求头则类似于:
User-Agent: Mozilla/5.0 ...
其中会包含浏览器版本、操作系统等信息。
除此之外,浏览器发送的请求头中通常还包含:
- Accept
- Referer
- Cookie
- Accept-Language
等额外信息。
因此,为了让爬虫程序更像浏览器,我们通常会将浏览器中的请求头复制出来,替换掉 requests 默认携带的请求头。
需要说明的是:
在实际开发中,并不一定需要完整复制浏览器的所有请求头。很多情况下,只需要补充 User-Agent、Referer、Cookie 等关键请求头即可。
不过对于初学者来说,直接复制浏览器中的完整请求头是一种简单且有效的调试方式,因此我们暂时采用这种方案。
此处推荐一个非常好用的 PyCharm 插件。
当我们从浏览器复制请求头之后,只需要右键点击 Headers,插件就能够自动帮我们生成 Python 字典格式,无需手动调整格式。
打开:
File -> Settings -> Plugins
搜索:
Headers
然后点击安装即可。
有些情况下安装完成后需要重启 PyCharm 才能生效。

安装好后,即可使用,使用方法如下,在需要粘贴header的地方右键即可,前提是你的粘贴板里面得有东西(也就是你需要先复制,再右键选headers):

python
import requests
url = 'http://www.boxofficecn.com/boxoffice2019'
my_header = {
"accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
"accept-encoding": "gzip, deflate",
"accept-language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6",
"cache-control": "no-cache",
"connection": "keep-alive",
"cookie": "Hm_lvt_b6d45668276623ae0dd56fcf7dad2ead=1781858302,1782104979; Hm_lpvt_b6d45668276623ae0dd56fcf7dad2ead=1782104979; HMACCOUNT=17FF1E7A2152DB46; __tins__4287866=%7B%22sid%22%3A%201782104979229%2C%20%22vd%22%3A%201%2C%20%22expires%22%3A%201782106779229%7D; __51cke__=; __51laig__=1",
"host": "www.boxofficecn.com",
"pragma": "no-cache",
"referer": "https://mp.csdn.net/mp_blog/creation/editor/162127008?not_checkout=1&spm=1011.2124.3001.6192",
"upgrade-insecure-requests": "1",
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.0.0 Safari/537.36 Edg/149.0.0.0"
}
resp = requests.get(url=url,headers=my_header)
print(resp.request.headers)
然后在 get() 方法中传入我们自定义的 headers,再次打印验证,就会发现默认请求头已经被成功替换:
快代理案例(反爬问题)
学习完上述内容之后,我们需要找一个真正存在反爬机制的网站来测试一下,看看是不是不修改请求头,网站就能够识别出我们的爬虫程序。
测试网站:
本案例中,我们尝试爬取快代理网站中的免费代理 IP 数据。
按照之前介绍的步骤,进入网页后,按下 F12 打开开发者工具,切换到 Network 面板,然后刷新页面查看请求。

这里依然可以使用之前介绍的搜索关键词定位请求的方法来寻找数据来源。
不过这里再介绍一种更简单的分析思路:
首先判断,页面展示的数据是否直接包含在当前页面返回的 HTML 中。
什么叫数据在页面源码中?
简单来说,就是:
当前浏览器访问的 URL 返回的响应(Response)中,就已经包含了页面所需要展示的数据。
例如:
pythonhttps://www.kuaidaili.com/free/dps/1
但有些网站的数据并不直接存在于当前页面返回的 HTML 中,而是在页面加载完成之后,由 JavaScript 再去请求其他接口获取数据。
也就是说:
浏览器访问页面
↓
页面加载完成
↓
JavaScript 自动发送接口请求
↓
接口返回数据
↓
页面展示数据
对于这种情况,我们就需要继续分析这些异步接口。
而快代理这个页面则不同。
我们可以先查看当前页面请求的 Response,如果发现页面中展示的数据已经出现在响应内容里面,那么就说明:
当前页面返回的 HTML 中已经包含了所有数据,我们无需继续寻找额外接口。
还有一个简单的验证方法:
尝试切换页码。
例如:
https://www.kuaidaili.com/free/dps/1
切换到:
https://www.kuaidaili.com/free/dps/2
发现浏览器地址发生变化的同时,页面中的数据也发生了变化。
并且查看对应页面的 Response 后,可以发现新的数据已经包含在响应内容中。
这说明:
页面数据是随着当前 URL 一起返回的,而不是通过额外接口异步加载的。
查看 Response 验证之后,可以发现页面中的代理 IP 数据确实已经包含在响应内容里面。
免费私密代理IP(第2页)_IP代理_HTTP代理 - 快代理
既然已经确定了数据来源,那么我们就可以直接使用 requests 发起请求。
python
import requests
url = 'https://www.kuaidaili.com/free/dps/1'
resp = requests.get(url)
print(resp.text)
运行之后会发现:
返回的数据与浏览器中看到的内容完全不一样。

但是我们的 URL 明明填写正确了,为什么获取不到正常数据呢?
此时基本可以判断:
网站已经识别出了当前请求来自爬虫程序,并对请求进行了限制。
因此,我们需要尝试伪装浏览器请求。
最简单的方法就是:
将浏览器中的请求头复制出来,替换掉 requests 默认携带的请求头。
在复制请求头时需要注意:
请求头名称前面的冒号(:)不要复制,只保留键和值即可。

python
import requests
url = 'https://www.kuaidaili.com/free/dps/1'
my_header = {
"accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
"accept-encoding": "gzip, deflate, br, zstd",
"accept-language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6",
"cache-control": "no-cache",
"cookie": "_ss_s_uid=716da84cc6be76a00b6a68910554ba96; _gcl_au=1.1.1699039414.1775105081; _ga=GA1.1.443085113.1775105081; channelid=0; sid=1781922890362869; _uetsid=16e231106e0411f1a38a27721ec87e60|1j7gafz|2|g74|0|2364; _ga_DC1XM0P4JL=GS2.1.s1782110047$o7$g0$t1782110047$j60$l0$h0; _uetvid=a5eba6302e4e11f195cb21e44b69575b|upo5ba|1782111837956|4|1|bat.bing.com/p/conversions/c/a",
"pragma": "no-cache",
"priority": "u=0, i",
"referer": "https://www.kuaidaili.com/free/dps/1",
"sec-ch-ua": "\"Microsoft Edge\";v=\"149\", \"Chromium\";v=\"149\", \"Not)A;Brand\";v=\"24\"",
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": "\"Windows\"",
"sec-fetch-dest": "document",
"sec-fetch-mode": "navigate",
"sec-fetch-site": "same-origin",
"sec-fetch-user": "?1",
"upgrade-insecure-requests": "1",
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.0.0 Safari/537.36 Edg/149.0.0.0"
}
resp = requests.get(url, headers=my_header)
print(resp.text)
再次运行程序后,可以发现返回的数据已经与浏览器中的内容一致了。
由于返回的 HTML 文本非常长,我们可以直接搜索某个页面中的 IP 地址或者其他关键字进行验证。
如果能够搜索到页面中的数据,则说明:
我们已经成功绕过了网站最基础的请求头检测。

当然,需要再次强调的是:
修改请求头只能解决最基础的一类反爬问题。现代网站通常还会结合 Cookie、Token、请求签名、JavaScript 加密、IP 限制等多种手段进行反爬。
后续章节中,我们还会继续学习这些更加复杂的反爬机制。
百度案例(乱码问题)
有时候,我们成功获取到了服务器返回的数据,但是打印结果却发现是一堆乱码。
例如:
python
import requests
url = 'https://www.baidu.com/'
resp = requests.get(url)
print(resp.text)
运行后可能会出现类似下面的乱码情况:

这种情况通常并不是网站返回的数据有问题,而是:
程序使用的解码方式与网页实际使用的编码方式不一致。
简单来说:
服务器返回的是一种编码格式,而程序却按照另一种编码格式进行解码,自然就会出现乱码。
如果你想更深入地了解字符编码原理,可以阅读我之前写的文章:
【Java IO完全指南】文件、流、序列化详解-CSDN博客里面的【字符编码详解】这一章节。
那么如何解决乱码问题呢?
首先,我们可以查看网页源码或者响应头中的:
<meta charset="utf-8">

如果发现网页采用的是:
utf-8
编码,那么我们只需要手动指定:
python
import requests
url = 'https://www.baidu.com/'
resp = requests.get(url)
resp.encoding = 'utf-8'
print(resp.text)
再次运行后,乱码问题就消失了。

不过,手动分析网页编码再指定编码显然不够智能。
好在 requests 已经为我们提供了自动识别编码的方法:
python
# resp.encoding = 'utf-8'
resp.encoding = resp.apparent_encoding
apparent_encoding 会根据响应内容自动推测最可能的编码格式。
在大多数情况下,它都能够正确识别网页编码,从而解决乱码问题。
需要注意的是:
apparent_encoding****并不是读取网页中的 charset,而是通过分析文本内容来推测编码格式,因此并不能保证百分之百准确。
不过在绝大多数场景下,它已经完全够用了。
另外,现在很多网站都会在响应头中明确声明字符编码,因此 requests 往往能够自动识别编码,不需要我们手动指定。
只有在自动识别失败时,我们才需要手动修改:
resp.encoding
百度翻译(post请求案例)
前面我们学习的案例都是 GET 请求 ,接下来再来看一个 POST 请求 的案例,学习一下 POST 请求的参数应该如何携带。
本次以百度翻译作为案例进行讲解:

对于这种翻译网站,其实抓包会更加简单,因为我们并不需要刷新整个页面。
我们知道,每当在输入框中输入需要翻译的单词时,右侧就会立即显示对应的中文翻译。
实际上,这个过程就已经完成了一次完整的:
发送请求 -> 服务器处理 -> 返回响应
也就是说:
我们输入的内容会被发送到服务器,服务器翻译完成后,再将结果返回给浏览器进行展示。
因此,我们只需要在输入框中随便输入一些内容,就能够触发请求。
例如输入:
how are you
此时观察 Network 面板,可以发现浏览器正在不断发送请求。
这是因为:
只要输入框中的内容发生变化,浏览器就会重新发送一次翻译请求。
最后页面展示出来的翻译结果,其实就是最后一次请求返回的数据。

使用 Fetch/XHR 过滤请求
对于这种动态加载的数据,我们可以勾选:
Fetch/XHR
进行筛选。
前面我们一直使用的是:
All
选项,它会显示浏览器产生的所有请求。
而像百度翻译这种:
- 页面 URL 不变;
- 页面数据会动态变化;
的数据加载方式,通常称为:
异步加载(Ajax)
这种请求大多数都会出现在:
Fetch/XHR
分类中。
因此,勾选该选项后,可以过滤掉大量无关请求,使分析过程更加轻松。
由于:
how are you
比较长,并且输入过程中会不断触发请求,导致 Network 面板中的请求数量非常多,不利于观察。
所以建议:
-
先点击清空按钮清空面板;
-
输入一个较短的单词,例如:
cat
然后再次观察网络面板。

清空后:

经过筛选之后,请求数量明显减少了:

接下来,我们只需要依次点击这些请求,查看它们的响应内容即可。
当然,也可以使用前面介绍过的:
Ctrl + F
关键词搜索方式进行定位。
现在演示的是直接点击XHR里面每一个请求进行分析:

就这样依次点击,直到发现这一个请求的响应有我们要的数据:

所以确定是这个请求。
Preview 与 Response 的区别
当点击某个请求后,可以查看:
Preview
和:
Response
这两个选项。
其中:
Response
显示的是服务器返回的原始响应数据。
而:
Preview
则是浏览器对响应内容进行格式化之后的展示结果。
例如:
- HTML 页面会直接显示网页效果;
- JSON 数据会自动格式化;
- Unicode 编码有时也会自动转换成正常字符。
因此:
Preview 通常会比 Response 更容易阅读。
依次查看请求之后,很快就能够找到包含翻译结果的请求:
由此可以确定:
这就是我们需要分析的目标请求。
我们再来看一下它的 Response:

可以发现,响应中的中文全部变成了:
\u732b
这样的编码格式。
这也解释了为什么:
如果直接使用中文关键字搜索,很可能找不到对应请求。
如何使用关键字搜索请求?
例如搜索:
cat
你会发现搜索结果非常多。
不过其中很多请求其实可以直接排除,例如:
application
static/cat/
因为这些只是路径或者其他无关字段中包含了:
cat
而我们真正关心的是:
Response 或 Preview 中包含翻译数据的请求。
像下列图片红框圈起来的基本都可以排除掉,不是我们想要的请求。

按照这种思路筛选,很快就能够定位到正确请求。
分析请求
查看请求详情:

可以发现:
请求方式为:
POST
并且多出了一个:
Payload
区域。
这个区域就是:
POST 请求提交参数的地方。
其中可以看到:

kw : cat
这里:
kw
表示参数名(Key)。
cat
表示参数值(Value)。
由于:
Form Data
本质上就是:
键 = 值
的形式。
因此,在 Python 中通常使用:
dict(字典)
来表示。
对应代码如下:
data = {
"kw": "cat"
}
这里的参数同样可以借助之前介绍的 Header 插件快速复制生成。
代码实现:
python
import requests
url = 'https://fanyi.baidu.com/sug'
data = {
"kw": "cat"
}
my_header = {
"accept": "*/*",
"accept-encoding": "gzip, deflate, br, zstd",
"accept-language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6",
"cache-control": "no-cache",
"connection": "keep-alive",
"content-length": "6",
"content-type": "application/x-www-form-urlencoded",
"cookie": "换成你自己的cookie",
"host": "fanyi.baidu.com",
"origin": "https://fanyi.baidu.com",
"pragma": "no-cache",
"referer": "https://fanyi.baidu.com/mtpe-individual/transText?ext_channel=Aldtype",
"sec-ch-ua": "\"Microsoft Edge\";v=\"149\", \"Chromium\";v=\"149\", \"Not)A;Brand\";v=\"24\"",
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": "\"Windows\"",
"sec-fetch-dest": "empty",
"sec-fetch-mode": "cors",
"sec-fetch-site": "same-origin",
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.0.0 Safari/537.36 Edg/149.0.0.0"
}
resp = requests.post(url=url, headers=my_header,data=data)
print(resp.text)

可以成功获取响应数据。
不过输出结果中的中文依然是:
\u732b
这种形式。
看起来并不是很友好。
JSON 响应处理
其实,服务器返回的数据类型是:
JSON
JSON 会自动将中文转换为 Unicode 编码。
因此:
对于 JSON 数据,我们应该优先使用 resp.json()****。
那么如何确定响应是不是 JSON 呢?
可以查看响应头:
Content-Type: application/json

只要看到:
application/json
基本就可以确定服务器返回的是 JSON 数据。
修改代码:
print(resp.json())
输出结果:

中文就能够正常显示了。
制作一个简单的翻译小程序
既然已经能够获取翻译结果,那么我们不妨把程序改造一下,做成一个简单的命令行翻译工具。
python
import requests
def start():
while 1:
kw = input("请输入你想要翻译的单词:")
data = {
"kw": kw
}
resp = requests.post(url=url, headers=my_header, data=data)
print("翻译结果:")
res_data = resp.json()['data']
for i in res_data:
print(i['k'])
print(i['v'])
choice = input("输入0退出程序,其余继续程序:").strip()
if choice == "0":
break
if __name__ == '__main__':
url = 'https://fanyi.baidu.com/sug'
my_header = {
"accept": "*/*",
"accept-encoding": "gzip, deflate, br, zstd",
"accept-language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6",
"cache-control": "no-cache",
"connection": "keep-alive",
"content-length": "6",
"content-type": "application/x-www-form-urlencoded",
"cookie": "你的cookie",
"host": "fanyi.baidu.com",
"origin": "https://fanyi.baidu.com",
"pragma": "no-cache",
"referer": "https://fanyi.baidu.com/mtpe-individual/transText?ext_channel=Aldtype",
"sec-ch-ua": "\"Microsoft Edge\";v=\"149\", \"Chromium\";v=\"149\", \"Not)A;Brand\";v=\"24\"",
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": "\"Windows\"",
"sec-fetch-dest": "empty",
"sec-fetch-mode": "cors",
"sec-fetch-site": "same-origin",
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.0.0 Safari/537.36 Edg/149.0.0.0"
}
start()
运行效果如下:

br 压缩导致的乱码问题
在爬取部分网站时,你可能会发现:
print(resp.text)
输出的内容是一堆乱码,甚至直接报错:
requests.exceptions.ContentDecodingError
出现这种情况,很可能是因为:
服务器返回的数据使用了 Brotli(br)压缩。
浏览器发送请求时,通常会携带:
Accept-Encoding: gzip, deflate, br
这表示:
浏览器支持:
- gzip
- deflate
- br(Brotli)
三种压缩格式。
如果服务器最终选择:
Content-Encoding: br
返回数据,而当前 Python 环境中又没有安装 Brotli 解码库,那么 requests 就无法正确解压响应数据。
最终就会出现:
- 乱码;
- 解码失败;
- ContentDecodingError 异常。
解决方法非常简单:
python
pip install brotli
安装完成后,requests 会自动识别并解压:
Content-Encoding: br
的数据。
无需修改任何代码。
如果你暂时不想安装额外库,也可以直接修改请求头:
将:
Accept-Encoding: gzip, deflate, br
改成:
Accept-Encoding: gzip, deflate
也就是:
主动告诉服务器:我不支持 br 压缩。
这样服务器通常就会退而选择:
gzip
格式进行压缩,从而避免乱码问题。
小技巧:如果在响应头中看到
Content-Encoding: br,而返回内容又出现乱码,可以优先检查是否安装了brotli库。
总结
本章我们正式进入了 Python 爬虫的学习,并完成了爬虫开发中最基础、也是最重要的一步------发送网络请求并获取服务器响应。
通过本章的学习,相信大家已经掌握了:
-
requests库的安装与使用; -
浏览器抓包的基本流程;
-
如何分析请求与响应;
-
GET 请求和 POST 请求的构造方法;
-
请求头(Headers)的作用;
-
最基础的反爬机制及应对方式;
-
常见的乱码问题及解决方案;
-
JSON 响应数据的处理方法。
需要再次强调的是:
爬虫的核心思想其实非常简单:找到数据来自哪个请求,然后用程序去模拟这个请求。
而抓包分析能力,则是整个爬虫学习过程中最重要的能力之一。
在下一章中,我们将开始学习:
-
如何从获取到的网页源码中提取自己真正需要的数据;
-
XPath 的基本语法与使用方法;
-
正则表达式在爬虫中的应用;
-
不同数据提取方式之间的区别与选择。
后续章节中,我们还会陆续讲解:
-
图片爬取;
-
视频爬取;
-
验证码识别(ddddocr);
-
Cookie 与 Session;
-
JavaScript 逆向;
-
App 逆向等内容。
至此,我们已经迈出了爬虫学习的第一步。下一章,正式开始进入数据解析阶段。