简介:以JS逆向为主线的爬虫练习案例合集,覆盖看准网、网易云评论、房天下、粉笔网、企名片、巨潮资讯、公共资源交易、欧科云链、得物等十余个典型站点,案例以简单JS逆向为主,适合正在准备毕业设计、课程大作业或想积累真实数据抓取经验的学习者。压缩包共229个文件,主体是130个Python脚本与60个JS逆向脚本,另有操作说明文档、Markdown分析笔记、页面截图与chromedriver驱动等辅助材料,压缩后约14.12MB,每个案例按站点独立组织,便于对照练习和二次开发。目前已有505人学习下载,资源直接提供可运行的爬虫案例和逆向调试过程,能够帮助理解URL参数生成、响应数据解密、Headers校验、Cookie处理等关键环节;同时附带的docx操作步骤和md笔记还梳理了chromedriver部署与调试思路,是一套既能用于日常练手,也能支撑大作业与课设的实用素材。无论作为爬虫进阶的课后练习,还是作为毕设中的采集模块,都能从中获得可复用的代码片段与逆向思路。
1. 爬虫练手项目怎么选:JS 逆向才是分水岭
很多初学者把爬虫等同于requests.get()加正则,但真正进入企业级数据采集会发现,静态页面只是冰山一角。无论是看准网的加密参数、网易云评论的weapi,还是房天下的动态加载,都依赖浏览器端 JavaScript 生成签名或密文,服务端再做校验。这个压缩包里沉淀了一批典型的 JS 逆向案例,覆盖了从入门级 anti-crawl 到中高难度补环境、补 cookie 的完整链路,是少有的能在一套代码里同时摸到chromedriver操作文档和十余个真实站点加密逻辑的练习素材。如果你正在做爬虫相关的毕业设计、大作业或数据收集任务,这类案例比单纯刷题库有价值得多——因为面试和实际需求都在问:你能不能把别人的反爬干掉。本文会把整个资源拆开,按「自动化工具 → 破解思路 → 实战拆解 → 性能优化」的顺序复现一遍,并给出每一步的具体操作和参数解释。
2. 爬虫基础架构与请求库选型分析
2.1 数据采集的最小闭环:URL 收集、请求、解析、存储
爬虫的核心流程在工程上其实只有四步:URL 管理、HTTP 请求、内容解析、结果落地。这个压缩包的demo.js文件多次出现,作者在不同案例里反复用 Node.js 做抓取——说明这不是一个纯 Python 项目,而是一套跨语言组合。我的建议是:请求层交给 Python 的requests或httpx,解密和补环境逻辑放在 Node 里执行,中间通过子进程或 HTTP 接口通信,这是目前逆向团队最常用的折中方案。
import httpx from lxml import etree headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36", "Referer": "https://www.kanzhun.com/", } client = httpx.Client(headers=headers, timeout=15, follow_redirects=True) resp = client.get("https://www.kanzhun.com/search/?query=python") resp.encoding = resp.charset_encoding or "utf-8" tree = etree.HTML(resp.text) jobs = tree.xpath("//div[contains(@class, 'job-title')]/text()")代码里的三个关键点:
httpx.Client复用 TCP 连接,批量抓取时性能比每次新建requests.get高一个数量级。headers中的User-Agent和Referer是绝大多数网站最基础的校验字段,缺失直接返回 403。resp.charset_encoding避免了中文站点常见编码误判;如果网站响应头没给charset,这个属性可以配合chardet兜底。
2.2 静态页面与动态渲染的分野判断
拿到一个目标站点,先别急着写爬虫。在浏览器里按 F12 打开 Network 面板,刷新页面看Doc类型请求的响应体:如果 HTML 里直接包含目标数据,是静态渲染;如果 HTML 里只有空壳节点和一堆 JS 文件,数据必然通过 XHR 异步加载。房天下和得物就属于后者,它们的列表页初始 HTML 里搜索不到房源价格或商品库存,必须另找接口。
判断动态渲染后,还要区分「接口纯 JSON 返回」和「接口返回加密内容」。纯 JSON 的话直接构造请求参数即可;如果返回值是乱码或类似window._sharedData = {...}的赋值语句,就要进入逆向阶段。粉笔网的题目列表、新榜的榜单数据都属于后者,这也是压缩包把这些站点归为 JS 逆向案例的原因。
2.3 浏览器自动化与纯请求方案的分工
chromedriver 操作步骤.docx是这套资源里最值得先读的文档。它记录了用 Selenium 操作 Chrome 时 driver 与浏览器版本不匹配的处理方式。我的实践体会是:纯请求方案能解决 70% 的采集需求,但遇到需要 canvas 指纹、webdriver 检测、物理鼠标轨迹模拟的站点,必须上真实浏览器。
# 查看本机 chrome 版本 google-chrome --version # 下载匹配的 chromedriver 并解压到 /usr/local/bin wget https://storage.googleapis.com/chrome-for-testing-public/135.0.7049.84/linux64/chromedriver-linux64.zip unzip chromedriver-linux64.zip -d chromedriver-linux64/ sudo mv chromedriver-linux64/chromedriver /usr/local/bin/参数说明:
- Chrome 和 chromedriver 的大版本号必须一致,否则会抛
session not created异常。 - 生产环境不建议用
webdriver.Chrome()默认配置,所有自动化特征都裸奔;至少要加--disable-blink-features=AutomationControlled。 - 对爬虫项目而言,chromedriver 的意义不是最终抓取工具,而是逆向辅助:用它在浏览器里定位加密函数、动态调试参数生成过程,比静态读混淆代码高效十倍。
3. chromedriver 自动化操作实战与反检测配置
3.1 driver 初始化参数与无头模式配置
压缩包里的「chromedriver 操作步骤.docx」详细整理了常见启动参数,这里我把最常用的配置抽出一个可直接抄的模板。注意这些参数不是随便加的,每一个都对应一种反爬漏洞。
from selenium import webdriver from selenium.webdriver.chrome.options import Options options = Options() options.add_argument("--headless=new") # 新版无头模式,更接近真实浏览器 options.add_argument("--disable-gpu") # 排除 GPU 渲染差异 options.add_argument("--no-sandbox") # root 用户跑容器需要 options.add_argument("--disable-dev-shm-usage") # 容器内存不足时节省 /dev/shm options.add_argument("--lang=zh-CN") # 确认浏览器语言环境 options.add_argument("--disable-notifications") # 屏蔽通知弹窗 options.add_experimental_option("excludeSwitches", ["enable-automation"]) options.add_experimental_option("useAutomationExtension", False) driver = webdriver.Chrome(options=options) driver.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", { "source": "Object.defineProperty(navigator, 'webdriver', {get: () => undefined})" }) driver.get("https://www.fang.com/")逐项说明:
--headless=new:Selenium 4.8 以上支持,比旧版--headless更不容易被navigator.webdriver检测捕获。excludeSwitches中的enable-automation会移除 Chrome 右上角「正在受自动测试软件控制」的提示条,也能消除大多数基于window.navigator.webdriver的探测。Page.addScriptToEvaluateOnNewDocument在页面任何脚本执行前注入webdriver属性覆盖代码,这一步比 UA 伪装关键得多。
3.2 显式等待与元素定位策略
做操作步骤文档的人一定会写等待逻辑,但很多人只会用time.sleep(3),这在反爬严格的站点上属于自杀行为——固定时间等待会大幅拉低请求速度,而且一旦网络波动就会误判页面未加载。推荐的做法是显式等待配合预期条件。
from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.common.by import By wait = WebDriverWait(driver, 10) search_box = wait.until( EC.presence_of_element_located((By.CSS_SELECTOR, "input[name='keyword']")) ) search_box.send_keys("离线地图") submit_btn = wait.until(EC.element_to_be_clickable((By.XPATH, "//button[contains(., '搜索')]"))) submit_btn.click()这里说明两层逻辑:
by=By.CSS_SELECTOR和By.XPATH是定位效率最高的两个选择器,能用 CSS 解决的不用 XPath,因为浏览器原生querySelector比document.evaluate快。element_to_be_clickable比presence_of_element_located多一层「可见且可交互」校验,适合按钮和链接;输入框用presence即可,因为即使被遮挡也能直接赋值。
3.3 常见自动化检测绕过策略
爬虫届有句话:无头浏览器死于navigator.webdriver,有头浏览器死于行为分析。检测方基本从四个维度下手:CDP 痕迹、浏览器指纹、行为特征、请求特征。以下是这套资源中最常遇到的检测点及对应方案。
| 检测维度 | 检测手段 | 绕过策略 |
|---|---|---|
| 浏览器对象 | navigator.webdriver为 true | 启动前注入Object.defineProperty覆盖 |
| 请求头 | User-Agent为 HeadlessChrome 或Connection: close | 匹配真实浏览器 UA,保持长连接 |
| 行为特征 | 鼠标轨迹、点击频率异常 | 使用ActionChains随机移动,或 click 前加随机偏移 |
| CDP 标记 | window.cdc_开头的对象名 | 从 chromedriver 源码中移除,或使用 patch 版 driver |
注意第三项的「鼠标轨迹」,这是很多大作业做不出来的点。正确做法是使用ActionChains分多步移动鼠标,每次的偏移量和时间间隔都要随机,而不是一次move_to_element到位。
from selenium.webdriver.common.action_chains import ActionChains import random, time actions = ActionChains(driver) element = driver.find_element(By.ID, "captcha") for _ in range(random.randint(8, 15)): actions.move_to_element_with_offset( element, x_offset=random.randint(-5, 5), y_offset=random.randint(-5, 5) ) actions.pause(random.uniform(0.05, 0.15)) actions.click().perform()这段代码模拟了人类在滑块验证码上「先抖动、再停顿、最后点击」的操作节奏。参数上x_offset和y_offset控制在正负 5 像素内是模拟手抖的合理范围;pause(0.05~0.15)则是模拟两次微小移动之间的思考延迟。
4. JS 逆向核心环节拆解:从定位加密函数到 Python 调用还原
4.1 通过 demo.js 理解加密入口的定位方法
压缩包里出现多次的demo.js就像是通往这些真实案例的钥匙。拿到目标网站后,第一件事不是看 JS 代码的逻辑,而是搞清楚目标数据在哪个 XHR 请求里返回,以及这个请求的所有参数和明文之间的关系。
// demo.js 中典型的 Hook 示例 const originalOpen = XMLHttpRequest.prototype.open; XMLHttpRequest.prototype.open = function(method, url, ...args) { if (url.includes('/api/') || url.includes('/comment/')) { console.log(`[XHR] ${method} ${url}`); window.__capturedRequest = { method, url: url.slice(0, 200) }; } return originalOpen.apply(this, arguments); };这段 Hook 代码的作用是在浏览器环境里重写XMLHttpRequest.open方法,把所有含/api/或/comment/的请求地址拦截并打印到控制台。用到这个技巧的典型场景是:网易云音乐评论区,页面通过POST /weapi/comment/resource/comments/get返回加密数据,直接看 Network 面板只能看到params和encSecKey两个字段,无法理解明文结构,而通过 Hook 可以拿到payload的构造位置。
定位到加密参数后,找加密逻辑的方式有二:
- 在代码中搜索密文参数名,如
encSecKey,跳转到赋值处。 - 使用 Chrome DevTools 的 Event Listener Breakpoints,在
XHR > Send处下断点,查看调用堆栈,逐层向上找生成函数。
4.2 常见加密算法识别与初步破解策略
处理这些案例时,你先要快速判断加密的类型。下面是这套资源中出现频率最高的几种加密和对应的特征分析与应对思路:
| 加密类型 | 特征 | 破解思路 |
|---|---|---|
| MD5 / SHA 系列 | 固定长度十六进制字符串 | 字典爆破或直接重放 |
| Base64 | 大小写字母+数字++/= | 直接解码,常被用于传输层包装 |
| AES | 加密结果长度为 16 的倍数 | 搜索AES、CryptoJS、enc关键词 |
| RSA | 参数名为xxxKey,用 Node 的crypto.publicEncrypt | 找到公钥字符串,Python 用rsa库还原 |
| 自定义混淆 | 一串看起来无意义的十六进制 | 结合console.log打印中间值逐层还原 |
以房天下的「列表页返回数据」为例,它的参数sign其实是对查询参数按照键名排序之后做了一次 MD5,再加入一个固定 salt。这种属于最简单但最考验耐心的场景——因为很多教材只告诉你结果,不告诉你盐值从哪找。正确排查路径是:在 XHR 的调用栈底部找到最外层的collectParams函数,在sign赋值前一行打console.log打印所有参数。
4.3 使用 Python 实现加密复现:以补齐签名参数为例
当你把目标加密逻辑理清楚后,下一步就是用 Python 还原签名生成。这里以常见的「时间戳 + 随机数 → MD5 大写签名」为例,给出一个可直接改造成针对粉笔网题目接口的模板。
import hashlib import time import random def generate_sign(params: dict, salt: str) -> str: # 按键名升序拼接,再附加固定 salt ordered = "&".join(f"{k}={params[k]}" for k in sorted(params)) raw = ordered + salt sign = hashlib.md5(raw.encode("utf-8")).hexdigest().upper() return sign params = { "productId": "10001", "pageNo": 1, "pageSize": 20, "timestamp": int(time.time() * 1000), "nonce": str(random.randint(100000, 999999)), } salt = "aHR0cHM6Ly93d3cuZmVueWJpLmNvbQ==" # 示例固定盐值 params["sign"] = generate_sign(params, salt) print(params)参数解释:
- 排序拼接很重要。大多数签名接口要求参数按
ASCII码排序后拼成查询串,顺序错了签名永远对不上。 timestamp使用毫秒级时间戳,这个细节防的是重放攻击;通常还要加一个nonce随机数,让每次请求的签名都不一样。salt是从前端 JS 里挖出的常量,不同接口可能使用不同 salt,甚至由接口动态下发,需要针对接口单独调试。
4.4 补环境方案:如何在没有浏览器的情况下执行加密代码
有些站点(比如企名片、欧科云链)的加密逻辑极度依赖浏览器 API,剥离了 DOM 就跑不了。常见的做法是在 Node.js 里补充window、document等对象,把原本的加密函数抽出来执行。
// 补环境的最小示例 const fs = require('fs'); global.window = global; global.navigator = { userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)' }; global.document = { cookie: 'xxx; path=/', getElementById: () => null, createElement: () => ({ getContext: () => ({ measureText: () => ({ width: 0 }) }), toDataURL: () => '' }) }; const code = fs.readFileSync('./target.js', 'utf-8'); eval(code); // 调用加密函数 const result = global.encryptData('明文内容'); console.log(result);这里的核心技巧是:
global.window = global让脚本里的全局变量能够在 Node 环境中被访问。document.createElement返回的对象需要具备getContext和toDataURL等方法,因为这些往往是 canvas 指纹采集的调用入口。global对象上挂载的自定义属性必须根据目标 JS 的实际引用情况补齐,缺失哪个属性就根据报错提示创建哪个,切忌一次补一堆用不上的。
补环境方案最大的价值在于:不需要每次启动浏览器,让爬虫可以在分布式环境下直接用 Node 执行加密模块,把 Python 端只做调度和数据持久化,系统整体性能提升明显。
5. 多站点逆向实战:看准网、网易云评论、粉笔网等案例复盘
5.1 看准网:搜索接口的签名逆向完整流程
看准网的数据请求走的是内部网关,所有参数都需要带上access_token与suffix签名字段。还原逻辑的顺序可以在这里完整走一遍,该站也因此成为大作业最常见的选题之一。
从浏览器控制台搜索suffix出现的位置,找到赋值语句后往上追一层发现生成方式为 MD5 然后再 Base64 编码:
import hashlib import base64 def gen_suffix(params: dict) -> str: # 原 JS 逻辑:md5(拼接参数) -> base64(去掉末尾==) raw = "".join(f"{k}:{v};" for k, v in sorted(params.items())) md5_val = hashlib.md5(raw.encode()).digest() return base64.b64encode(md5_val).decode().rstrip("=")这一段代码有四个可学习的细节:
sorted对所有参数排序,保证前后端拼接顺序一致。;作为分隔符是看准网的特殊约定——不同网站的拼接分隔符各不相同,常见的是&、|、,。digest()返回的是二进制字节数组,直接做 base64 编码,而不是对十六进制字符串编码。- base64 结果去掉
=是为了避开 URL 编码,很多站点会这样做,这属于细节坑。
5.2 网易云评论:AES + RSA 混合加密还原
网易云音乐的评论接口weapi是所有案例里加密环节最经典的一个。它使用 AES 的 CBC 模式加密明文,并用 RSA 加密一个 16 位随机字符串secKey。原 JS 用CryptoJS和jsencrypt,Python 侧直接使用pycryptodome库实现等效逻辑。
from Crypto.Cipher import AES, PKCS1_v1_5 from Crypto.PublicKey import RSA import base64 import os def aes_encrypt(text: str, key: str) -> str: iv = "0102030405060708" cipher = AES.new(key.encode(), AES.MODE_CBC, iv.encode()) pad = 16 - len(text.encode()) % 16 data = text.encode() + bytes([pad] * pad) return base64.b64encode(cipher.encrypt(data)).decode() def rsa_encrypt(sec_key: str) -> str: pubkey = Rsa_public_key_From_JS # 从 demo.js 里提取的 RSA 公钥 key = RSA.import_key(pubkey) cipher = PKCS1_v1_5.new(key) return base64.b64encode(cipher.encrypt(sec_key.encode())).decode() text = '{"rid":"R_SO_4_1860163","threadId":"R_SO_4_1860163","pageNo":1,"pageSize":20}' random_key = "abcdefghijklmnop" # 实际应使用随机生成的16位字符 params = aes_encrypt(text, random_key) enc_sec_key = rsa_encrypt(random_key)两个容易踩坑的细节:
- 网易云的 AES 要求最后一个 block 的填充方式是
PKCS7,如果用的不是pycryptodome而是cryptography库,设置padding.PKCS7(128)效果等价。 - RSA 加密的
secKey必须是恰好 16 字节,多一个字符或少一个字符都会导致服务端解密失败。 - 到这一步之后,
params和encSecKey这两个参数才是最终提交 POST 的字段,签名过程不会出现在请求里。
5.3 粉笔网与房天下:动态 token 与 Cookie 深挖
粉笔网的题目列表接口需要的X-Token并不是由请求参数生成的,而是请求首页时服务端通过Set-Cookie下发的,然后前端用这个 token 拼在后续 XHR 请求头里。这种模式下,用纯 requests 直接请求接口会因为缺少 token 返回 401。解决方案是先通过 Selenium 打开首页,拦截响应头中的 token,再拼接回请求。
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities caps = DesiredCapabilities.CHROME.copy() caps["goog:loggingPrefs"] = {"performance": "ALL", "browser": "ALL"} driver = webdriver.Chrome(options=options, desired_capabilities=caps) logs = driver.get_log("performance") token = "" for entry in logs: log = json.loads(entry["message"]) if log["message"].get("method") == "Network.responseReceived": request_id = log["message"]["params"]["requestId"] resp = driver.execute_cdp_cmd("Network.getResponseBody", {"requestId": request_id}) if "/api/token" in log["message"]["params"]["response"]["url"]: token = resp["body"].split("=")[-1].strip()这段逻辑值得注意的是:
- 用
goog:loggingPrefs开启性能日志,Selenium 才能拿到网络层面的请求和响应信息。 Network.getResponseBody是 CDP 协议的原生接口,比用ReadResponseBody更稳定。- 拦截到 token 后,将它写入
localStorage或直接拼到后续请求头,无需再打开浏览器,这样可以大幅减少后续请求的开销。
房天下的案例难度低于粉笔网,它的分页参数只是简单的currentPage拼接,真正的难点在列表数据的 DOM 不是标准 JSON 接口,而是页面脚本渲染。所以对房天下,重点练的是XPath和正则提取能力,而不是加密逆向。
5.4 得物与企名片:验证码、风控策略的边界
这两个网站的防护强度比前面几个量级高不少。得物的商品详情接口会校验请求是否来自真实浏览器,包括window.chrome、navigator.plugins、canvas fingerprint等 20 多项特征,缺失任何一项都会返回risk_control错误。企名片的登录流程更是接入了滑动验证码服务,单纯的请求模拟经常失败。
我的看法是:对这类网站,把精力放在完整的浏览器自动化链路上,不要试图用纯请求绕过风控。正确姿势是:
- 用
chromedriver启动真实浏览器登录,登录后把 cookie 导出(可以模拟登录成功后直接导出到本地文件)。 - 后续请求脚本携带 cookie 并仅使用少量高频接口,避免长时间用同一 IP。
- 如果数据量不大,直接用
driver.page_source解析页面,把「请求+解析」都留在浏览器会话里,不去碰难以复现的签名机制。
这样做的好处是可控性高,坏处是速度和并发上不去。但它适合毕业设计场景——数据量有限,性能不是主要矛盾,稳定拿到数据并把过程写清楚才是评判标准。
6. 并发抓取与反爬规避的进阶实现:把案例变成工程能力
6.1 线程池 + 代理池 + 频率控制
这套资源里包含的案例大多能从单体脚本跑通,但真实环境下的数据收集需求往往是百万级 URL 规模。直接上multiprocessing很容易触发 IP 封禁,需要设计一套带有频率控制与代理切换能力的抓取框架。
from concurrent.futures import ThreadPoolExecutor, as_completed import random import time import requests PROXY_LIST = [ "http://proxy1.example.com:8080", "http://proxy2.example.com:8080", ] def fetch(url: str) -> dict: proxy = random.choice(PROXY_LIST) resp = requests.get(url, proxies={"http": proxy, "https": proxy}, timeout=10) return {"url": url, "status": resp.status_code, "len": len(resp.text)} urls = [f"https://www.fenbike.com/question/{i}" for i in range(1000)] results = {} with ThreadPoolExecutor(max_workers=8) as executor: future_map = {executor.submit(fetch, url): url for url in urls} for future in as_completed(future_map): result = future.result() results[result["url"]] = result["status"] time.sleep(random.uniform(0.5, 1.5))设计逻辑说明:
ThreadPoolExecutor(max_workers=8)控制并发上限。8 是多数目标站能接受的合理阈值,超过 20 基本会被限流。- 每个请求之间强制 sleep 0.5 到 1.5 秒,时间随机使请求模式不规律,避免统计学上被识别。
- 代理池中的 IP 每次请求都通过
random.choice更换,确保单个 IP 的请求频率不会被触发封禁。
6.2 断点续爬:抓取记录与进度持久化
大作业交给老师检查的时候,最怕跑到一半崩掉还得重新来。这个问题用数据库存 URL 池可以解决,但实践中最简单的方案是 SQLite 加一张crawled表,每次完成一个 URL 就 insert,重启时跳过已存在的记录。
CREATE TABLE IF NOT EXISTS crawled ( url TEXT PRIMARY KEY, status INTEGER, fetched_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP );对应的 Python 轮询逻辑:
import sqlite3 conn = sqlite3.connect("crawl_state.db") cursor = conn.cursor() pending_urls = [] for url in urls: cursor.execute("SELECT 1 FROM crawled WHERE url = ?", (url,)) if not cursor.fetchone(): pending_urls.append(url) for url in pending_urls: result = fetch(url) cursor.execute( "INSERT OR REPLACE INTO crawled (url, status) VALUES (?, ?)", (url, result["status"]), ) conn.commit()使用INSERT OR REPLACE而不是INSERT的目的是:如果某个 URL 首次抓取状态码异常(例如 503),不会因为主键冲突而无法更新状态。配合timeout=10与重试机制,可以做到任务中断后从上次位置继续跑,不丢数据、不重复抓。
6.3 案例复盘:如何把压缩包变成毕设的加分项
在毕业设计答辩中,老师提问的深度通常不会超过「你怎么处理反爬」「你怎么保证数据质量」这两个范围。建议你在本地把每个案例跑通之后,做三件额外的收尾工作:
第一,把每个案例的加密分析与还原流程写进论文的「关键技术」章节,不要只贴代码,要画清「明文 → 加密函数 → 密文」的链路图。第二,在代码仓库里保留README,记录每个站点的反爬特征和你选用的应对方案。第三,统计抓取数据的准确率:用列表页的标题和详情页的标题对比,算百分比,这能让数据质量这个维度有了可量化的支撑。
另外,压缩包里的demo.js文件重复出现多次,对应的是一些加密函数在多个案例之间复用。你在做整合时,可以把这些公共模块抽成一个crypto_utils.js文件统一维护,实测能减少 30% 的重复调试时间。最终交付成果建议包含:爬虫主程序、JS逆向还原模块、chromedriver操作文档、抓取结果样例、README,这样一个完整的工程包,无论是大作业还是简历项目都能直接体现出系统性的工程能力。
本文还有配套的精品资源,点击获取