四重采集通道
上一讲我们已经知道:
爬虫的核心问题不是“怎么写代码”,而是先判断: 数据到底在哪里?
这一讲就围绕这个问题展开。
同样是抓职位信息,数据可能出现在四个地方:
HTML 页面里
JavaScript 渲染后的页面里
接口 JSON 里
浏览器偷偷请求到的接口响应里
所以就对应四种采集通道:
1. 静态 HTML 采集
2. 动态页面采集
3. API 直连采集
4. on_response 响应监听采集
1. 第一重:静态 HTML 采集
1.1 它是什么?
静态 HTML 采集,就是:
直接请求网页源码
↓
拿到 HTML
↓
从 HTML 里提取数据
这是最传统、最基础的爬虫方式。
比如网页源码里直接有:
<div class="job">
<h2>数据分析师</h2>
<span class="company">某科技公司</span>
<span class="salary">15k-25k</span>
</div>
那我们就可以直接从 HTML 里提取:
职位名称:数据分析师
公司名称:某科技公司
薪资:15k-25k
1.2 生活化理解
你可以把它理解为:
网页把菜单直接贴在墙上
你拍一张照片
然后从照片里抄菜名
数据已经在页面源码里了,不需要点击,不需要等待,不需要浏览器执行 JavaScript。
1.3 最小代码长什么样?
import requests
from bs4 import BeautifulSoup
url = "https://example.com/jobs"
response = requests.get(url)
html = response.text
soup = BeautifulSoup(html, "html.parser")
jobs = soup.select(".job")
for job in jobs:
title = job.select_one("h2").get_text(strip=True)
company = job.select_one(".company").get_text(strip=True)
salary = job.select_one(".salary").get_text(strip=True)
print(title, company, salary)
这段代码做了几件事:
requests.get(url) 请求网页
response.text 拿到 HTML 字符串
BeautifulSoup(html) 把 HTML 变成可查询对象
soup.select(".job") 找到所有职位块
select_one("h2") 找职位标题
get_text(strip=True) 提取干净文本
示例
import requests
from bs4 import BeautifulSoup
url = "http://www.zwwwblog.top/share/category_%E5%BE%AE%E6%9C%8D%E5%8A%A1"
response = requests.get(url)
html = response.text
soup = BeautifulSoup(html, "html.parser")
blogs = soup.select(".blogItemsMainContain")
print(f"共找到 {len(blogs)} 条内容\n")
print("=" * 80)
for i, blog in enumerate(blogs, 1):
title = blog.select_one("#blogItemTitle").get_text(strip=True)
Summary = blog.select_one("#blogItemSummary").get_text(strip=True)
img_tag = blog.select_one("#blogItemSummaryImgContain img")
ImgContain = img_tag['src'] if img_tag else ''
print(f"\n第 {i} 条:")
print(f"标题:{title}")
print(f"简介:{Summary}")
print(f"头图:{ImgContain}")
print("-" * 80)
1.4 它适合什么场景?
适合:
学校就业网公告列表
简单企业官网招聘页
服务端直接渲染的列表页
不需要登录、不需要点击、不需要滚动的页面
比如很多学校就业网站,列表和详情内容直接在 HTML 里,这种就可以优先考虑静态 HTML 采集。
通用项目里的学校采集链路,本质上就是“列表页解析 + 明细页抓取”的两段式采集:先抽取列表容器 HTML,再进入详情页提取正文内容。
1.5 优点和缺点
优点:
速度快
代码简单
资源消耗小
不需要打开浏览器
适合大批量抓取
缺点:
遇到 JavaScript 动态加载会抓不到数据
遇到复杂点击、滚动、分页不方便
页面结构一变,解析规则容易失效
2. 第二重:动态页面采集
2.1 它是什么?
动态页面采集,就是用程序控制一个真实浏览器。
常见工具是:
Playwright
Selenium
我们现在重点看 Playwright,因为已有项目大量使用它。
动态页面采集流程是:
启动浏览器
↓
打开网页
↓
等待页面加载
↓
点击筛选项
↓
滚动页面
↓
点击下一页或加载更多
↓
从渲染后的页面里提取数据
2.2 生活化理解
静态 HTML 采集像是“直接看纸质菜单”。
动态页面采集像是:
你真的进了一家餐厅
点开菜单
点击分类
翻页
展开详情
再把内容抄下来
它更像真人操作浏览器。
2.3 最小代码长什么样?
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch(headless=False)
page = browser.new_page()
page.goto("https://example.com/jobs")
# 点击“加载更多”
page.click("text=加载更多")
# 等待职位卡片出现
page.wait_for_selector(".job-card")
jobs = page.query_selector_all(".job-card")
for job in jobs:
title = job.query_selector(".title").inner_text()
company = job.query_selector(".company").inner_text()
print(title, company)
browser.close()
这段代码对应的动作是:
launch() 启动浏览器
new_page() 打开新标签页
goto() 访问网页
click() 点击按钮
wait_for_selector() 等待某个元素出现
query_selector_all() 找到多个元素
inner_text() 提取页面文字
示例
import time
from playwright.sync_api import sync_playwright
# 目标网站地址
url = "http://www.zwwwblog.top/share/Index"
# 启动 Playwright 浏览器自动化框架
with sync_playwright() as p:
# 启动 Chromium 浏览器,headless=False 表示显示浏览器窗口
browser = p.chromium.launch(headless=False)
# 创建一个新的浏览器标签页
page = browser.new_page()
print("正在访问网页...")
try:
# 访问网站首页,设置60秒超时,DOM加载完成即可继续
page.goto(url, timeout=60000, wait_until="domcontentloaded")
print("网页加载成功!")
except Exception as e:
# 如果页面加载失败,关闭浏览器并退出程序
print(f"网页加载失败: {e}")
browser.close()
exit()
print("等待导航栏加载...")
try:
# 等待导航栏的"分类"按钮出现,最多等待10秒
page.wait_for_selector("#category", timeout=10000)
print("导航栏加载成功!")
except Exception as e:
# 如果导航栏加载失败,关闭浏览器并退出程序
print(f"导航栏加载失败: {e}")
browser.close()
exit()
print("\n鼠标悬浮到'分类'菜单...")
# 获取"分类"菜单元素
category_menu = page.query_selector("#category")
# 模拟鼠标悬浮操作,触发下拉菜单显示
category_menu.hover()
# 等待1.5秒,让下拉菜单动画完成
time.sleep(1.5)
print("获取所有分类选项...")
# 获取下拉菜单中所有的分类链接元素
categories = page.query_selector_all("#categoryDropDown a.menuLinkStyle")
# 存储分类信息的列表
category_list = []
# 遍历所有分类元素,提取名称和链接
for category in categories:
# 提取分类名称,去除空白并按换行符分割,取第一行
category_name = category.inner_text().strip().split('\n')[0]
# 获取分类链接的 href 属性
category_href = category.get_attribute("href")
# 将分类信息保存到字典中
category_list.append({
'name': category_name,
'href': category_href
})
print(f"共找到 {len(category_list)} 个分类\n")
# 遍历所有分类,逐个抓取内容
for idx, cat_info in enumerate(category_list, 1):
# 获取当前分类的名称和链接
category_name = cat_info['name']
category_href = cat_info['href']
print("=" * 80)
print(f"\n正在抓取第 {idx}/{len(category_list)} 个分类: {category_name}")
print(f"分类链接: {category_href}")
print("-" * 80)
try:
# 重新获取"分类"菜单元素(避免元素引用失效)
category_menu = page.query_selector("#category")
# 悬浮到分类菜单,显示下拉列表
category_menu.hover()
# 等待1秒让下拉菜单完全显示
time.sleep(1)
# 根据 href 属性精确定位要点击的分类链接
category_link = page.query_selector(f"a[href='{category_href}']")
# 点击该分类链接
category_link.click()
print("等待内容加载...")
# 等待笔记列表容器加载完成,最多等待30秒
page.wait_for_selector(".blogItemsMainContain", timeout=30000)
# 额外等待2秒,确保所有内容完全渲染
time.sleep(2)
# 获取当前分类下所有的笔记卡片元素
blogs = page.query_selector_all(".blogItemsMainContain")
print(f"\n该分类共找到 {len(blogs)} 条内容:\n")
# 遍历当前分类的所有笔记
for i, blog in enumerate(blogs, 1):
# 查找标题元素
title_elem = blog.query_selector("#blogItemTitle")
# 查找简介元素
summary_elem = blog.query_selector("#blogItemSummary")
# 查找头图 img 元素
img_container = blog.query_selector("#blogItemSummaryImgContain img")
# 提取标题文本,如果元素不存在则返回默认值
title = title_elem.inner_text().strip() if title_elem else '无标题'
# 提取简介文本,如果元素不存在则返回默认值
summary = summary_elem.inner_text().strip() if summary_elem else '无简介'
# 获取图片链接,如果元素不存在则返回默认值
img_src = img_container.get_attribute("src") if img_container else '无头图'
# 格式化输出笔记信息
print(f" [{i}] 标题: {title}")
# 如果简介超过50字,截断显示
if len(summary) > 50:
print(f" 简介: {summary[:50]}...")
else:
print(f" 简介: {summary}")
print(f" 头图: {img_src}")
print()
print(f"✓ 分类 '{category_name}' 抓取完成!\n")
except Exception as e:
# 捕获抓取过程中的异常,打印错误信息但不中断程序
print(f"✗ 抓取分类 '{category_name}' 时出错: {e}\n")
# 如果不是最后一个分类,返回首页准备抓取下一个
if idx < len(category_list):
print("返回首页准备下一个分类...")
# 直接访问首页,而不是使用后退按钮(更稳定)
page.goto(url, timeout=60000, wait_until="domcontentloaded")
# 等待2秒让页面完全加载
time.sleep(2)
# 等待导航栏加载完成,为下一次循环做准备
page.wait_for_selector("#category", timeout=10000)
print("\n" + "=" * 80)
print("所有分类抓取完成!")
# 关闭浏览器
browser.close()
2.4 它适合什么场景?
适合:
必须点击筛选项才出现数据
必须滚动到底部才加载更多
页面是 Vue / React / Angular 渲染的
职位详情在弹窗或新 tab 中打开
需要模拟移动端页面
需要处理前端路由
比如企业招聘官网常见这些情况:
点击城市
点击职位类别
点击加载更多
滚动页面加载下一批职位
详情页是前端路由
通用项目里的企业采集链路就增强了动态加载和多分页能力,包括自动滚动加载、加载更多按钮、分页函数和新开 tab 抓详情等设计。
2.5 项目中的例子
猎聘项目中,脚本会用 Playwright 打开页面,并通过浏览器真实操作筛选项。比如选择企业职位、公司性质、公司规模、经验等,然后进入抓取流程。猎聘脚本不是直接拼 URL,而是通过页面控件驱动筛选,这就是典型的动态页面采集。
同时,猎聘脚本还会启动本地 Chromium、设置代理、关闭自动化特征、拦截图片/字体/媒体资源,目的是更像真实浏览器并降低资源消耗。
2.6 优点和缺点
优点:
适应复杂页面
可以点击、滚动、翻页
可以处理 JavaScript 渲染
接近真实用户行为
缺点:
速度慢
资源消耗大
浏览器容易崩
并发成本高
更容易遇到反爬
选择器容易受页面改版影响
所以真实项目里一般不会无脑全用 Playwright。
能用接口就用接口;接口不好搞,再用 Playwright。
3. 第三重:API 直连采集
3.1 它是什么?
API 直连采集,就是不抓网页,而是直接请求网站背后的数据接口。
比如网页上看到职位列表,其实浏览器背后可能请求了这个接口:
https://example.com/api/jobs?page=1&city=shanghai
接口返回的不是 HTML,而是 JSON:
{
"jobs": [
{
"title": "数据分析师",
"company": "某科技公司",
"salary": "15k-25k",
"city": "上海"
}
]
}
这时候我们就可以直接请求接口。
3.2 生活化理解
动态页面采集像是:
去餐厅点菜,再从菜单上抄
API 直连像是:
直接找后厨要一份电子菜单表格
它更直接、更干净。
3.3 最小代码长什么样?
import requests
url = "https://example.com/api/jobs"
params = {
"page": 1,
"city": "shanghai",
"keyword": "数据分析"
}
response = requests.get(url, params=params)
data = response.json()
for job in data["jobs"]:
title = job["title"]
company = job["company"]
salary = job["salary"]
city = job["city"]
print(title, company, salary, city)
这段代码的重点是:
不解析 HTML
不打开浏览器
直接解析 JSON
示例
import requests
import time
import json
# API 接口地址(从浏览器开发者工具 Network 面板中找到的真实接口)
api_url = "https://www.zwnsyw.top/api/dress/listByCursor"
# 设置请求体数据(完全按照浏览器实际发送的格式)
payload = {
"category": "",
"cursor": "1916105854908362754",
"pageSize": 12,
"searchText": "",
"sortField": "id",
"sortOrder": "ascend",
}
# 设置完整的请求头(POST 请求需要指定 Content-Type)
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": "application/json, text/plain, */*",
"Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8",
"Accept-Encoding": "gzip, deflate, br",
"Content-Type": "application/json",
"Referer": "https://www.zwnsyw.top/waterfall",
"Origin": "https://www.zwnsyw.top",
"Connection": "keep-alive",
"Sec-Fetch-Dest": "empty",
"Sec-Fetch-Mode": "cors",
"Sec-Fetch-Site": "same-origin"
}
print("=" * 80)
print("开始 API 直连采集...")
print("=" * 80)
try:
# 发送 POST 请求到 API 接口(注意:使用 json 参数自动序列化)
print("\n正在请求 API 接口...")
print(f"请求 URL: {api_url}")
print(f"请求方式: POST")
print(f"请求体: {json.dumps(payload, ensure_ascii=False, indent=2)}\n")
# 使用 json 参数,requests 会自动将字典序列化为 JSON 字符串
response = requests.post(api_url, json=payload, headers=headers, timeout=10)
# 打印响应状态码
print(f"响应状态码: {response.status_code}")
# 检查响应状态码
if response.status_code == 200:
print("✓ HTTP 请求成功!\n")
# 解析 JSON 响应数据
try:
data = response.json()
# 检查接口返回的业务状态码
if data.get("code") == 0:
print("✓ 接口返回数据正常!\n")
# 获取数据列表
records = data["data"]["records"]
cursor = data["data"].get("cursor", "") # 下一页的游标
is_last = data["data"].get("last", False) # 是否最后一页
print(f"共获取到 {len(records)} 条服饰数据")
print(f"当前游标: {cursor if cursor else '首页'}")
print(f"是否最后一页: {is_last}")
print("-" * 80)
if len(records) == 0:
print("\n⚠️ 未获取到数据\n")
else:
# 遍历每条服饰记录
for idx, dress in enumerate(records, 1):
# 提取基本信息
name = dress.get("name", "未知名称")
introduction = dress.get("introduction", "无简介")
story = dress.get("story", "无故事")
# 提取服饰属性
ethnic_group = dress.get("ethnicGroup", "未知")
epoch = dress.get("epoch", "未知朝代")
location = dress.get("location", "未知地点")
category = dress.get("category", "未知分类")
tags = ", ".join(dress.get("tags", []))
# 提取图片链接(需要拼接完整 URL)
base_url = "https://www.zwnsyw.top"
picture_url = base_url + dress.get("pictureUrl", "") if dress.get("pictureUrl") else "无图片"
# 提取用户信息
user = dress.get("user", {})
user_name = user.get("userName", "未知用户")
user_avatar = user.get("userAvatar", "无头像")
# 格式化输出
print(f"\n[{idx}] {name}")
if len(introduction) > 60:
print(f" 简介: {introduction[:60]}...")
else:
print(f" 简介: {introduction}")
print(f" 民族: {ethnic_group} | 朝代: {epoch} | 地点: {location}")
print(f" 分类: {category} | 标签: {tags}")
print(f" 图片: {picture_url}")
print(f" 上传者: {user_name}")
print("-" * 80)
print("\n✓ 数据采集完成!")
print("=" * 80)
else:
print(f"✗ 接口返回业务错误")
print(f"错误码: {data.get('code')}")
print(f"错误信息: {data.get('message', '未知错误')}")
print(f"错误描述: {data.get('description', '无')}")
except json.JSONDecodeError as e:
print(f"✗ JSON 解析失败: {e}")
print(f"响应内容前500字符: {response.text[:500]}")
else:
print(f"✗ HTTP 请求失败,状态码: {response.status_code}")
print(f"响应内容: {response.text[:500]}")
except requests.exceptions.Timeout:
print("✗ 请求超时,请检查网络连接")
except requests.exceptions.ConnectionError:
print("✗ 连接失败,请检查网络或 API 地址")
except Exception as e:
print(f"✗ 发生未知错误: {type(e).__name__}: {e}")
import traceback
traceback.print_exc()
3.4 它适合什么场景?
适合:
接口容易找到
接口参数简单
接口不需要复杂签名
接口返回字段完整
接口稳定
比如很多企业招聘官网、部分校招官网、部分职位系统,其实都能找到职位接口。
通用项目里就有 auto_api 采集通道,原理是绕开前端渲染,直接请求官方职位接口,然后把接口数据映射成内部 detail_*.json 字段模型。
3.5 怎么发现接口?
用浏览器开发者工具。
步骤大概是:
打开网页
↓
按 F12
↓
进入 Network 面板
↓
刷新页面
↓
筛选 Fetch / XHR
↓
点击请求
↓
看 Response 里有没有职位 JSON
如果看到 Response 里有:
{
"jobName": "产品经理",
"workCity": "北京",
"salary": "20k-30k"
}
那就说明这个接口可能可以用。
3.6 优点和缺点
优点:
最快
最干净
最省资源
字段结构清晰
不依赖页面样式
适合大规模采集
缺点:
接口可能有签名
接口可能需要 Cookie
接口参数可能复杂
接口可能有频控
接口可能随版本变化
有些详情字段不在接口里
所以 API 直连是首选,但不是永远可行。
4. 第四重:on_response 响应监听采集
4.1 它是什么?
这是非常实用的一种中间方案。
它的思路是:
我不直接破解接口
也不只从 DOM 抠文字
我让浏览器正常打开页面
然后监听浏览器收到的接口响应
也就是说:
页面自己去请求接口
我在旁边“偷听”接口返回的数据
这就是 on_response。
4.2 生活化理解
API 直连像是你直接去问后厨:
把电子菜单给我
on_response 像是:
你坐在前台旁边
等服务员从后厨拿菜单出来
你顺手看一眼菜单内容
你不用自己知道后厨怎么下单,也不用完全破解接口参数。
4.3 最小代码长什么样?
from playwright.sync_api import sync_playwright
def handle_response(response):
url = response.url
if "/api/jobs" in url:
try:
data = response.json()
print("抓到接口数据:", data)
except:
pass
with sync_playwright() as p:
browser = p.chromium.launch(headless=False)
page = browser.new_page()
page.on("response", handle_response)
page.goto("https://example.com/jobs")
page.wait_for_timeout(5000)
browser.close()
关键代码是:
page.on("response", handle_response)
意思是:
只要页面收到任何网络响应
就交给 handle_response 处理
然后我们在里面判断:
if "/api/jobs" in response.url:
意思是:
只处理职位接口
其他图片、CSS、JS 都忽略
4.4 它适合什么场景?
适合:
页面背后有接口
但接口参数复杂
接口有签名
接口需要浏览器环境
接口需要先点击筛选项才能触发
直接 requests 请求很麻烦
DOM 解析又不稳定
这类场景非常常见。
4.5 项目中的例子
猎聘就是很典型的 on_response 方案。
猎聘脚本通过页面真实操作触发搜索接口,然后监听猎聘搜索接口返回的数据,把 jobCardList 保存起来。之后再从页面 DOM 中拿职位跳转链接,通过职位 ID 去接口 JSON 里反查对应职位基础字段。这样比纯 DOM 抓列表更稳定。
猎聘公司版也监听公司职位条件接口和公司职位列表接口,用接口返回的条件集合生成抓取组合,而不是完全依赖页面上渲染出来的筛选项。
通用项目里也有 auto_on_response 通道,原理是在页面对象上注册 page.on('response', handler),匹配目标接口 URL,然后提取响应 JSON 并落盘为内部结构。
4.6 优点和缺点
优点:
不用完全破解接口参数
比纯 DOM 抓取稳定
可以拿到结构化 JSON
适合复杂动态网站
能配合点击、筛选、滚动使用
缺点:
仍然要启动浏览器
速度比 API 直连慢
需要判断哪些 response 是目标接口
接口响应时机不好控制
如果页面不触发接口,就监听不到
5. 四种采集通道怎么选?
可以用这个判断顺序:
第一步:先看 HTML 源码里有没有数据
有 → 静态 HTML 采集
没有 → 看 Network 里有没有接口 JSON
有,而且接口好请求 → API 直连
有,但接口参数复杂 / 签名复杂 → on_response 监听
没有明显接口,必须点击/滚动/渲染后才有 → Playwright 动态页面采集
更直观一点:
数据直接在 HTML 里
→ requests + BeautifulSoup
数据在接口 JSON 里,接口好调
→ requests + json
数据在接口 JSON 里,但接口不好直接调
→ Playwright + page.on("response")
数据要靠点击、滚动、渲染才出现
→ Playwright 操作页面
6. 四种方式对比
| 采集通道 | 核心工具 | 数据来源 | 速度 | 稳定性 | 难度 | 适合场景 |
|---|---|---|---|---|---|---|
| 静态 HTML | requests + BeautifulSoup | HTML 源码 | 快 | 中 | 低 | 简单公告页、服务端渲染页面 |
| 动态页面 | Playwright | 渲染后的 DOM | 慢 | 中 | 中 | 点击、滚动、加载更多、复杂前端 |
| API 直连 | requests | JSON 接口 | 很快 | 高 | 中 | 接口清晰、参数简单 |
| on_response | Playwright | 浏览器收到的接口响应 | 中 | 高 | 中高 | 接口复杂但页面能触发 |
7. 用招聘网站举例
假设我们要抓一个招聘网站。
场景一:学校就业网公告
页面源码里直接有:
<a href="/detail/123">某公司2026校园招聘</a>
适合:
静态 HTML 采集
流程:
请求公告列表
↓
解析公告标题和链接
↓
请求详情页
↓
提取正文
场景二:企业官网职位页
页面打开后职位才慢慢出现,需要滚动或点加载更多。
适合:
动态页面采集
流程:
打开页面
↓
滚动到底部
↓
点击加载更多
↓
等职位卡片出现
↓
提取职位链接
场景三:企业官网职位接口
Network 里发现:
/api/recruit/jobs?page=1
返回 JSON。
适合:
API 直连采集
流程:
请求接口
↓
解析 JSON
↓
保存 detail_*.json
场景四:猎聘这类复杂平台
页面需要真实操作筛选项,接口又有复杂参数。
适合:
动态页面 + on_response 监听
流程:
打开猎聘搜索页
↓
点击筛选条件
↓
监听职位搜索接口
↓
拿到职位列表 JSON
↓
打开职位详情页补抓长文本
↓
保存并入库
这正是猎聘项目的核心路线:页面操作、接口监听、详情页补抓、落本地 JSON,再调用内部接口入库。
8. 现在要建立的关键认知
不要把爬虫理解成“我会 requests”或者“我会 Playwright”。
真正的爬虫思维是:
先判断数据在哪里
再选择采集通道
再决定解析方式
最后考虑稳定性和工程化
也就是:
数据在 HTML?
用 HTML 解析。
数据在 JSON?
用接口解析。
接口不好直接请求?
用 response 监听。
页面必须交互?
用浏览器自动化。
数据在图片?
后面再加 OCR。
9. 这四种通道之间不是互斥的
真实项目里经常组合使用。
比如猎聘:
Playwright 打开页面
+ 点击筛选条件
+ on_response 监听列表接口
+ DOM 抓详情页字段
+ 本地 JSON 落盘
+ 内部接口入库
这不是单一通道,而是组合拳。
再比如通用采集系统:
有些站点走 DOM
有些站点走 API
有些站点走 on_response
后面统一进入 HTML 清洗、模型抽取、质控上传
它的设计思想就是“不押注单一抓取方式”,而是根据站点差异使用多通道采集冗余。
10. 小结
数据在哪里?
↓
┌────────────────┼────────────────┐
↓ ↓ ↓
HTML JSON 渲染后页面
↓ ↓ ↓
静态 HTML 采集 API 直连采集 Playwright 动态采集
↓
接口复杂但页面能触发?
↓
on_response 监听
爬虫不是固定用某一种技术,而是根据数据出现的位置选择采集通道。
组内导航:⬅️ 爬虫是什么 | 🏠 00-爬虫课程六讲 | ➡️ 爬虫的基础积木——每个动作能得到什么效果
💬 评论