很多初学者学 Python 爬虫,往往是从requests.get(url)打印 HTML 开始的。但一旦进入真实项目,你会发现事情远没有这么简单:页面数据在接口里,接口参数是加密的,Cookie 是动态生成的,甚至一段完整的签名算法隐藏在压缩混淆过的 JavaScript 代码中。如果你只会对着静态 HTML 做解析,遇到稍微有点防护的网站就会寸步难行。这篇文章不是为了教你绕过某个具体站点的风控,而是从 Python 爬虫基础到 JS 逆向分析,梳理一套完整的知识体系和实战路径。无论你是刚入门的学生,还是工作中需要采集公开数据的开发者,都可以按这个框架逐步搭建自己的爬虫能力树。
1. 爬虫与 JS 逆向的核心概念
1.1 网络爬虫的本质
网络爬虫,本质上是一个自动化的 HTTP 客户端程序。它通过模拟浏览器或移动端 App 的请求行为,向服务器发送 HTTP 请求,接收响应,再从响应数据中提取我们需要的信息。整个过程可以拆解为四步:发起请求、获取响应、解析数据、持久化存储。
在应用场景上,爬虫通常可以划分为三类:
- 批量型爬虫:针对某一类数据源,一次性或定期抓取大量数据。比如抓取某公开网站的新闻列表、商品信息、招聘岗位等。
- 增量型爬虫:只抓取新增或变化的数据。例如监控某个页面的价格、库存变化,每次只处理上次抓取之后的差异部分。
- 垂直型爬虫:聚焦特定领域或特定站点,结构更精细,解析规则更定制化。比如专门爬取某行业政策文件、专利数据或学术论文信息。
初学者往往会把爬虫理解为“写个脚本跑一下”,但真正工程化的爬虫必须考虑频率控制、异常重试、去重、增量更新、数据存储、日志监控等一系列问题。爬虫不是一把梭的脚本,而是一套严谨的数据采集工程。
1.2 什么是 JS 逆向
JS 逆向,全称是 JavaScript 逆向分析。当服务器返回的 HTML 或 JavaScript 代码中包含了数据加密、参数签名、动态 Cookie 生成等逻辑时,我们不能直接获得最终数据,而必须分析前端 JavaScript 的执行过程,弄清楚数据是怎么生成的,然后在 Python 中复现或调用这段逻辑,从而拿到有效的请求参数和最终数据。
一个典型的场景是这样的:目标网站的接口地址是公开的,但每个请求必须携带一个sign参数,这个参数由时间戳、设备信息和用户 ID 通过一段自定义加密算法生成。如果你只是用requests.get(url)直接请求接口,服务器会返回 403 或错误码。只有先分析 JS 文件,理解签名算法,才能构造出合法的请求。
很多同学会问:为什么不直接用 Selenium 或 Playwright 模拟浏览器?当然可以,但无头浏览器开销大、并发能力低,而且在数据量较大的场景下效率远不如直接构造 HTTP 请求。动态渲染工具适合小型任务,而造出加密参数去直连接口,是高效率采集的核心能力。
1.3 为什么 JS 逆向是爬虫进阶的分水岭
早期的爬虫,直接请求 URL 就能拿到 HTML。后来网站开始做前后端分离,数据改由异步接口返回,爬虫只要多抓一次 XHR 请求也能解决。但再往后,反爬技术升级了:接口请求参数加入签名、Cookie 动态化、频控策略服务端化、字体反爬、图片滑块验证码……
这些反爬机制大多数都依赖 JavaScript 在前端完成加密或计算。这就导致一个结果:模拟浏览器不是不行,但代价太高;要高效采集,必须理解前端加密逻辑。所以,能不能读懂 JS、能不能定位关键加密函数、能不能复现签名算法,就成了区分基础爬虫和高级爬虫的分水岭。
同时需要强调一点:JS 逆向不是为了攻击某个网站,而是为了研究前端加密技术、理解数据接口的通信方式。在实际项目中,需要遵守目标网站的 robots 协议和服务条款,控制请求频率,仅处理公开且合法的数据。
2. 环境准备与基础工具链
2.1 Python 环境安装与配置
Windows 环境建议直接从 Python 官网下载安装包,安装时务必勾选“Add Python to PATH”。macOS 和 Linux 一般自带 Python 3,但版本可能较旧,建议使用 Homebrew 或 apt 安装较新的版本。
本文示例以 Python 3.10+ 为基础,版本不同时部分 API 或依赖可能略有差异。安装完成后,在终端中执行:
python --version如果输出类似Python 3.10.12,说明安装成功。Windows 下如果提示“python 不是内部或外部命令”,需要检查环境变量是否正确。
2.2 推荐的开发工具
我强烈建议使用 VSCode 搭配 Python 插件,轻量且好用。当然,PyCharm 也是很好的选择,尤其是进行大型爬虫项目调试时,它的断点调试体验更好。
VSCode 中需要安装的插件:
- Python(Microsoft 官方)
- Pylance
- Jupyter(如果你习惯用 notebook 做探索式分析)
如果你做 JS 逆向分析,还需要一个前端调试环境。其实不必专门安装 WebStorm,直接在浏览器 DevTools 中分析就足够了,配合 VSCode 临时修改 JS 代码时使用。
2.3 抓包与调试工具
浏览器 DevTools 是 JS 逆向最核心的工具。
打开 Chrome 或 Edge,按 F12 打开 DevTools,最常用的面板有:
- Network:查看所有网络请求,筛选 XHR/Fetch,查看请求头、请求参数、响应内容。
- Sources:查看 JS 文件源码,设置断点,逐步调试。
- Elements:查看页面 DOM 结构,辅助编写 CSS 选择器。
- Console:执行调试代码,临时验证 JS 逻辑。
对于更复杂的抓包场景,可以补充使用 Fiddler 或 Charles。这类工具可以捕获 PC 端或手机端 App 的 HTTPS 流量,是在没有 WebView 页面时分析移动端接口的重要手段。
2.4 安装必要的 Python 库
为了完成后面的实战,需要先安装几个高频库:
pip install requests beautifulsoup4 lxml pyexecjs pandas各库作用如下:
| 库名称 | 主要用途 |
|---|---|
| requests | 发送 HTTP 请求,是整个爬虫的基础 |
| beautifulsoup4 | 解析 HTML/XML 文档,配合 lxml 解析引擎 |
| lxml | 高性能解析引擎,支持 XPath |
| PyExecJS | 在 Python 中执行 JavaScript 代码 |
| pandas | 数据处理与存储,导出 Excel/CSV 非常方便 |
版本无需刻意固定为某一个,按照当前环境的最新稳定版安装即可。
3. Python 爬虫基础语法与 Requests 库实战
3.1 一文理解 HTTP 请求核心要素
在写第一行请求代码之前,先理清 HTTP 请求的关键构成。服务器识别你,靠的是三样东西:
- 请求行:包含请求方法(GET/POST 等)、URL 和协议版本。
- 请求头(Headers):包含 User-Agent、Referer、Cookie、Content-Type 等,服务器通过请求头判断客户端类型。
- 请求体(Body):POST 请求携带的表单数据、JSON 数据等。
其中User-Agent是最基础的反爬判断字段。服务器会检查这是一个真实浏览器的 UA,还是一个爬虫脚本的默认 UA。所有请求都应该设置一个完整的 UA,模拟常见浏览器的标识。
3.2 Requests 的 GET 与 POST 请求
GET 请求通常用于获取数据。最简单的写法是:
import requests url = "https://httpbin.org/get" response = requests.get(url, timeout=10) print(response.status_code) print(response.text)实际项目中,我们会在请求中带上参数和请求头:
import requests url = "https://httpbin.org/get" params = { "page": 1, "size": 20 } 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", "Referer": "https://httpbin.org/", "Accept-Language": "zh-CN,zh;q=0.9" } response = requests.get(url, params=params, headers=headers, timeout=10) print(response.url) print(response.json())POST 请求通常用于提交数据、登录、搜索等操作:
import requests url = "https://httpbin.org/post" payload = { "username": "test_user", "password": "123456" } 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", "Content-Type": "application/x-www-form-urlencoded" } response = requests.post(url, data=payload, headers=headers, timeout=10) print(response.json())注意data参数用于表单格式,json参数则用于发送 JSON 格式:
response = requests.post(url, json=payload, headers=headers, timeout=10)需要根据接口要求的Content-Type来选择发送方式。
3.3 Session 会话管理:维持登录状态
爬虫中最常见的需求是登录后访问受限资源。requests 库中,使用requests.Session()可以自动维持 Cookie,避免每次请求手动携带 Cookie:
import requests session = requests.Session() session.headers.update({ "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" }) # 第一次请求,服务器会下发 Cookie session.get("https://httpbin.org/cookies/set?name=csdn") # 第二次请求自动携带 Cookie resp = session.get("https://httpbin.org/cookies") print(resp.text)Session 在爬虫中的作用有两个:一是保持登录态,二是模拟同一个浏览器的多次连续请求,降低被识别为脚本的概率。
3.4 超时与异常处理:写爬虫的第一步防御
网络请求是天然不稳定的,必须处理超时、连接失败、HTTP 错误等异常。最基础的防御写法是:
import requests from requests.exceptions import RequestException def safe_request(url, **kwargs): try: response = requests.get(url, timeout=10, **kwargs) response.raise_for_status() return response except RequestException as e: print(f"请求失败: {e}") return None这样的封装虽然简单,但已经能避免因为网络抖动导致整个程序崩溃。
4. 数据解析:从 HTML 到结构化数据
4.1 BeautifulSoup 基础用法
拿到 HTML 之后,需要从页面中提取数据。BeautifulSoup 是目前最友好的 HTML 解析库,它支持 CSS 选择器,写起来直观清晰:
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/120.0.0.0 Safari/537.36" } url = "https://example.com/news" response = requests.get(url, headers=headers, timeout=10) response.encoding = response.apparent_encoding # 避免中文乱码 soup = BeautifulSoup(response.text, "lxml") # 使用 CSS 选择器定位所有新闻条目 for item in soup.select("div.news-list li a"): title = item.get_text(strip=True) href = item.get("href") print(title, href)get_text(strip=True)可以提取标签下的纯文本并去除首尾空白。get("href")获取属性值。对于更复杂的页面,可能还需要嵌套选择:
items = soup.select("div.news-item") for item in items: title = item.select_one("h2.title") summary = item.select_one("p.summary") published_at = item.select_one("span.date") if title: print("标题:", title.get_text(strip=True)) if summary: print("摘要:", summary.get_text(strip=True)) if published_at: print("发布时间:", published_at.get_text(strip=True))4.2 正则表达式与 XPath 的补充
CSS 选择器无法表达过于复杂的文本模式时,可以结合正则表达式。比如从文本中提取日期:
import re text = "发布时间:2026-01-15 10:30:00" match = re.search(r"\d{4}-\d{2}-\d{2}", text) if match: print("日期:", match.group())如果使用 lxml 或 parsel 库,也可以选择 XPath 定位。XPath 在处理复杂层级时更灵活:
from lxml import html tree = html.fromstring(response.text) titles = tree.xpath('//div[@class="news-list"]//li/a/text()') hrefs = tree.xpath('//div[@class="news-list"]//li/a/@href')但初学者建议先掌握 CSS 选择器,因为 DevTools 复制的选择器可以直接用,学习成本更低。
4.3 完整的翻页爬虫实战:新闻列表抓取
下面用一个完整的案例,把请求、解析、翻页、存储串起来。这里以某个公开新闻站点为例,域名替换为你实际项目中合法访问的站点地址即可。
import requests from bs4 import BeautifulSoup import csv import time 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" } def fetch_page(page): url = f"https://example-news.com/list?page={page}" try: response = requests.get(url, headers=headers, timeout=10) response.encoding = response.apparent_encoding if response.status_code == 200: return response.text except requests.RequestException as e: print(f"第 {page} 页请求失败: {e}") return None def parse_html(html): soup = BeautifulSoup(html, "lxml") result = [] for item in soup.select("div.news-list li a"): title = item.get_text(strip=True) href = item.get("href") if title and href: result.append([title, href]) return result def save_to_csv(rows, filename="news.csv"): with open(filename, "w", newline="", encoding="utf-8-sig") as f: writer = csv.writer(f) writer.writerow(["标题", "链接"]) writer.writerows(rows) all_data = [] for page in range(1, 6): print(f"正在抓取第 {page} 页...") html = fetch_page(page) if html: page_data = parse_html(html) all_data.extend(page_data) time.sleep(1) # 控制频率,避免对服务器造成压力 save_to_csv(all_data) print(f"共抓取数据 {len(all_data)} 条")这个案例涵盖了爬虫的完整链路:构造请求、处理编码、编写解析逻辑、翻页、去重(可选)、导出 CSV。把它吃透,你已经能应对大部分静态页面的抓取需求。
5. JS 逆向核心原理:从定位到复现
5.1 服务端渲染与前后端分离的差异
要理解 JS 逆向,先要理解数据接口的变化。传统网站是服务端渲染,HTML 里直接包含数据;现代网站大多采用前后端分离架构,HTML 只是一个空壳,数据由 JavaScript 动态调用接口并渲染到页面。
这带来的变化是:直接请求 URL 拿到的 HTML 中并没有数据,数据在异步接口里。我们在 Network 面板中筛选 XHR,找到返回数据的接口,直接请求接口往往也能拿到数据。
但问题又来了:服务端为了限制采集,在接口请求中增加了签名参数、动态 Cookie、加密 Token 等。这些参数由前端 JS 生成,服务端在收到请求后进行校验。于是,爬虫进阶的核心就从“怎么解析 HTML”,转移到了“怎么模拟生成合法的请求参数”。
5.2 常见的前端反爬手段
前端反爬手段多种多样,以下是开发中常见的形式:
| 手段 | 现象 | 应对思路 |
|---|---|---|
| 请求头校验 | 缺少某个 header 时返回 403 | 补齐 User-Agent、Referer、Origin 等 |
| 加密参数 | 接口带 sign、token、nonce 等参数 | 定位加密函数,复现签名逻辑 |
| 动态 Cookie | Cookie 中某字段由 JS 生成 | 分析 Cookie 生成算法或通过 JS 执行生成 |
| 验证码 | 登录/请求时出现滑块或图形验证码 | 登录态缓存、打码平台、行为模拟 |
| 字体反爬 | 页面文字显示正常,源码中乱码 | 分析字体映射,下载字体文件还原 |
| JS 混淆 | 关键代码被压缩、变量名不可读 | 扣代码、补环境、断点调试 |
| 接口返回加密数据 | 响应内容为密文 | 定位解密函数,在 Python 或 JS 中解密 |
5.3 逆向分析的核心流程
JS 逆向不是靠猜,而是有一套可执行的流程:
- 抓包定位接口:在 Network 面板筛选 XHR,找到返回目标数据的接口,看它发出了哪些请求参数。
- 全局搜索参数名:在 Sources 或全局搜索中,搜索
sign、token、cookie等关键词,找到 JS 中生成该参数的位置。 - 打断点调试:在可疑代码行打断点,刷新页面,观察函数调用栈,确认参数生成过程。
- 追踪加密函数:进入相关函数,分析它接收了哪些输入,经过什么算法,最终输出什么。
- 复现或调用:用 Python 重写这段逻辑,或者用 PyExecJS/Node.js 直接执行原生 JS 代码。
- 验证请求:用构造出的参数发起请求,比对结果是否与浏览器一致。
5.4 断点调试:定位加密过程的必经之路
浏览器 DevTools 是逆向分析的主战场。在 Sources 面板中,你可以这样操作:
- 在可疑代码左侧点击行号,添加断点。
- 刷新页面,代码执行到断点处会暂停。
- 右侧 Watch 面板添加需要观察的变量。
- 点击“Step into next function call”进入函数内部。
有一个很实用的技巧:在 Network 面板中,右键接口请求,选择“Copy as fetch”,然后在 Console 中粘贴执行。如果请求成功,说明关键参数可以绕过或复用;如果失败,说明需要进一步分析参数生成逻辑。
5.5 补环境与扣代码
当 JS 加密逻辑复杂且涉及浏览器环境时,直接复制全部 JS 代码到 Python 中执行往往会出现“环境缺失”的问题,因为前端代码可能会检测window、document、navigator等浏览器对象。此时有两种思路:
- 补环境:在 Python 或 Node 中,把这些浏览器对象模拟出来,变量检测能通过即可。这是 JS 逆向中的高级操作。
- 扣代码:只抠出最关键的一小段加密函数,不引入整个 JS 文件,最大程度降低环境依赖。
初学者建议先从小段函数入手,逐步补环境,不要一上来就尝试运行整个混淆脚本。
6. 实战案例:模拟动态 Cookie 与签名参数
6.1 场景设计与问题拆解
假设我们需要请求一个公开数据接口,服务端要求每个请求携带一个动态生成的 Cookie 字段dynamic_token。这个字段的值由服务端登录后下发的seed和当前时间戳通过一个 JS 函数计算得到。直接请求时因为没有合法dynamic_token,会被拒绝。
这本质上就是“动态 Cookie 反爬”的基本模型。我们需要做两件事:
- 分析 JS 文件,找到生成
dynamic_token的算法。 - 在 Python 中执行这段 JS 代码,构造合法 Cookie。
6.2 准备 JS 环境:用 PyExecJS 执行 JS 代码
先安装依赖:
pip install pyexecjs然后在 Python 中编译并调用 JS 函数:
import execjs js_code = """ function generateToken(seed, timestamp) { var str = seed + "_" + timestamp; var hash = 0; for (var i = 0; i < str.length; i++) { var char = str.charCodeAt(i); hash = ((hash << 5) - hash) + char; hash = hash & hash; } return Math.abs(hash).toString(16); } """ ctx = execjs.compile(js_code) result = ctx.call("generateToken", "a1b2c3d4", "1737000000") print(result)这里定义了一个纯 JS 写的字符串哈希函数,不依赖浏览器环境,也不依赖 Node.js 内置模块,可以在任何支持 JS 引擎的环境中运行。真实项目中的加密算法可能更复杂,但执行方式本质相同。
6.3 完整代码:登录获取 Seed 并生成动态 Cookie
下面把流程完整整合起来。假设登录接口返回一个 JSON,里面包含seed字段:
import requests import execjs import time import json 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", "Content-Type": "application/json" } # 第一步:模拟登录,获取 seed login_url = "https://example-api.com/login" login_payload = { "username": "test_user", "password": "test_password" } login_resp = requests.post(login_url, json=login_payload, headers=headers, timeout=10) login_data = login_resp.json() seed = login_data.get("seed") if not seed: print("登录失败,未获取到 seed") exit() print("获取到的 seed:", seed) # 第二步:使用 PyExecJS 生成 dynamic_token js_code = """ function generateToken(seed, timestamp) { var str = seed + "_" + timestamp; var hash = 0; for (var i = 0; i < str.length; i++) { var char = str.charCodeAt(i); hash = ((hash << 5) - hash) + char; hash = hash & hash; } return Math.abs(hash).toString(16); } """ ctx = execjs.compile(js_code) timestamp = int(time.time()) dynamic_token = ctx.call("generateToken", seed, timestamp) print("生成的 dynamic_token:", dynamic_token) # 第三步:携带动态 Cookie 请求业务接口 data_url = "https://example-api.com/api/data" session = requests.Session() session.headers.update(headers) session.cookies.set("dynamic_token", dynamic_token, domain="example-api.com") resp = session.get(data_url, timeout=10) if resp.status_code == 200: print("请求成功,返回数据:") print(json.dumps(resp.json(), ensure_ascii=False, indent=2)) else: print("请求失败,状态码:", resp.status_code) print(resp.text)在这个案例中,核心步骤是:获取 seed → 构造 JS 执行环境 → 生成 token → 在请求中携带 Cookie。这种模式可以扩展到很多带有签名或动态 Cookie 的场景。
6.4 用 Python 重写加密逻辑
如果 JS 算法足够简单,我们也可以直接用 Python 重写,省掉 execjs 的依赖:
import hashlib import time seed = "a1b2c3d4" timestamp = str(int(time.time())) content = f"{seed}_{timestamp}" # 假设 JS 算法等价于 MD5 签名 dynamic_token = hashlib.md5(content.encode("utf-8")).hexdigest() print(dynamic_token)但现实项目中的算法往往比这复杂,可能包含字节位移、按位运算、S 盒替换等逻辑。此时直接用 Python 重写容易出错,优先用 PyExecJS 或 Node.js 执行原生 JS 代码,能最大程度保证一致性。
6.5 JS 逆向中的环境问题
上面案例中的 JS 函数非常简单,所以直接执行没有问题。但真实场景中,加密函数可能依赖window.btoa、navigator.userAgent、document.cookie等浏览器对象。遇到这类情况,你需要在 JS 代码前面补上环境模拟:
var window = {}; var navigator = { userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36" }; var document = { cookie: "" };补环境的核心原则是:缺什么补什么,用到什么定义什么。不需要把一整套浏览器环境都模拟出来,只要让 JS 执行不报错,并且输出结果与服务端期望一致即可。
7. 常见问题与排查思路
7.1 高频报错与解决方向
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
| 请求返回 403 | User-Agent 或 Referer 缺少;IP 被频控 | 补齐请求头,降低请求频率,使用代理池 |
| 请求返回 401 | Cookie 失效、登录态过期 | 重新登录,更新 Cookie |
| 接口返回 JSON 但数据是空 | 请求参数缺少签名或时间戳字段 | 检查签名生成逻辑,带上完整参数 |
| JS 文件内容是一堆压缩代码 | 代码经过压缩混淆 | 使用 Prettier 格式化,再搜索关键词定位 |
| PyExecJS 执行报错 | JS 执行环境缺失或语法不兼容 | 安装 Node.js 环境,检查 JS 语法 |
| 加载的中文乱码 | 网页编码未正确设置 | 设置resp.encoding = resp.apparent_encoding |
| 页面文字正常但源码乱码 | 字体反爬 | 下载 woff 字体文件,分析字符映射关系 |
| 动态 Cookie 每次都失效 | 生成逻辑依赖时间戳或随机数 | 找到生成入口,每个请求重新生成 |
7.2 定位不到加密函数怎么办
这是 JS 逆向中最常见也最头疼的问题。建议按下面的顺序排查:
- 看请求头:确认到底哪个参数是动态的。有些参数是写死在 JS 文件中的,有些是服务端返回的,不要一上来就逆向。
- 全局搜索:用 DevTools 的全局搜索,搜索参数名。比如
sign:、sign =、setCookie、document.cookie。 - 搜索含义词:如果参数名是混淆的变量,尝试搜索哈希算法特征词,如
md5、sha1、encrypt、hex、toString(16)。 - 断点溯源:在接口调用处打 XHR 断点,点击请求时暂停,查看调用栈,从栈底往上找到参数生成位置。
- 使用 Hook:在 JS 中定义
Object.defineProperty监听某个属性值的写操作,从而定位赋值来源。
7.3 关于验证码的补充
验证码不是 JS 逆向能完全解决的问题,它属于风控体系的一部分。应对策略通常是:降低请求频率、积累登录 Cookie、使用合法的验证码识别服务、或者评估是否需要人工介入。
值得强调的是,任何绕过验证码的行为都可能违反网站使用条款,在商业项目中务必评估法律合规风险。
8. 最佳实践与工程化建议
8.1 合规性与道德边界
这部分内容虽然放在后面,但其实是爬虫开发中最重要的一条红线。开发和测试过程中,应遵守以下原则:
- 只抓取公开数据,不突破访问控制措施。
- 尊重目标网站的 robots 协议。
- 控制请求频率,避免对服务器造成压力。
- 抓取的数据不用于商业竞争或其他侵权用途。
- 不涉及用户隐私数据、个人敏感信息。
- 在生产环境,务必通过法律顾问评估数据采集的合规性。
如果某个接口需要登录后访问,而你没有权限,那就不应该通过逆向手段强行访问。技术能力越高,越要对自己的行为边界有清晰认知。
8.2 请求频率控制与代理策略
爬虫的基础素养是“别把对方服务器打崩”。建议做到:
- 统一封装请求模块,内置随机延时。
- 对同一域名下的并发数做限制。
- 通过令牌桶或信号量控制请求速率。
- 高频采集时使用代理池,但注意筛选可靠代理,避免使用来历不明的免费代理导致安全问题。
一个更工程化的封装思路是:
import time import random import requests from requests.adapters import HTTPAdapter class RateLimitedClient: def __init__(self, min_interval=1.0, max_interval=3.0): self.session = requests.Session() self.min_interval = min_interval self.max_interval = max_interval self.last_request_time = 0 def request(self, method, url, **kwargs): now = time.time() elapsed = now - self.last_request_time wait_time = random.uniform(self.min_interval, self.max_interval) if elapsed < wait_time: time.sleep(wait_time - elapsed) response = self.session.request(method, url, **kwargs) self.last_request_time = time.time() return response8.3 异常重试与数据持久化
爬虫任务动辄跑几小时,任何一次网络抖动都不应该导致任务中断。核心思路是:
- 对可重试的异常(超时、5xx)做指数退避重试。
- 对不可重试的错误(404、参数错误)立即报错日志。
- 数据落盘时,优先选择 CSV、JSON、SQLite 或 MySQL,并在任务结束后做数据校验。
一个简单的重试示例:
import time import random from requests.exceptions import RequestException def retry_request(func, retries=3, base_delay=1): for attempt in range(retries): try: return func() except RequestException as e: if attempt == retries - 1: raise delay = base_delay * (2 ** attempt) + random.uniform(0, 1) print(f"请求异常,{delay:.2f} 秒后重试...") time.sleep(delay)8.4 日志与监控
生产级爬虫必须记录请求状态、成功量、失败量、异常原因。最简单的做法是使用 Python 标准库logging:
import logging logging.basicConfig( level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s", handlers=[ logging.FileHandler("crawler.log", encoding="utf-8"), logging.StreamHandler() ] ) logger = logging.getLogger(__name__) logger.info("开始抓取任务")有了日志,任务失败后你能快速定位是哪一步出了问题,而不是靠猜。
8.5 代码结构设计
爬虫代码不建议堆在一个脚本文件里。当项目逐渐变大,建议按下面结构组织:
crawler_project/ ├── crawler/ │ ├── __init__.py │ ├── client.py # 请求客户端封装 │ ├── parser.py # 数据解析模块 │ ├── storage.py # 数据存储模块 │ └── utils.py # 工具函数 ├── config.py # 配置文件 ├── main.py # 入口 └── requirements.txt # 依赖列表模块化的好处是:解析规则变更时,不需要改动网络层;存储方式变更时,不需要改动解析层。这是爬虫工程化的基本要求。
9. 总结与学习路线
9.1 本文核心要点回顾
这篇文章从 Python 爬虫基础出发,重点覆盖了以下几块内容:
- 爬虫的分类:批量型、增量型、垂直型的应用场景差异。
- HTTP 请求要点:requests 库的 GET/POST、Session 会话、异常处理。
- 数据解析:BeautifulSoup 的 CSS 选择器、正则表达式、翻页案例。
- JS 逆向原理:从抓包定位、全局搜索、断点调试到代码复现的完整流程。
- 实战案例:用 PyExecJS 执行 JS 生成动态 Cookie 和签名参数。
- 工程化建议:合规边界、频率控制、重试、日志、代码结构。
9.2 后续学习路线建议
如果你是从零开始,建议按照下面的顺序推进:
- 阶段一:Python 基础语法——重点是字符串、列表、字典、文件操作、函数、模块。
- 阶段二:Requests + BeautifulSoup——能完成静态页面的抓取和解析。
- 阶段三:动态接口分析——学习 DevTools 的 Network 面板,理解 XHR 请求和接口返回。
- 阶段四:JS 逆向入门——从简单的参数签名入手,掌握断点调试、全局搜索、PyExecJS 执行 JS。
- 阶段五:混淆对抗与补环境——格式化混淆代码、Hook 定位参数、补浏览器环境。
- 阶段六:Scrapy + 分布式爬虫——在单机任务跑通后,转向框架化开发和分布式调度。
如果你对异步爬虫感兴趣,可以在 requests 熟练后学习httpx和aiohttp,它们在高并发场景下更高效。如果对移动端数据感兴趣,则需要补充抓包工具(Fiddler/Charles)和 Android 调试相关知识。
最后说一点心里话:JS 逆向是一项需要大量实践的技术,它考验的是你阅读代码、分析和调试的能力。初期遇到困难是正常的,只要坚持按照“抓包 → 定位 → 分析 → 复现 → 验证”这条路径去练习,你会慢慢找到自己的节奏。我在实战中最大的体会是:不要急着找万能脚本,先搞清楚参数是怎么来的,你就已经解决了 80% 的问题。希望这篇文章能帮你少走一些弯路,也欢迎你在评论区分享自己遇到的逆向场景,我也很期待看到更多真实的案例讨论。