--- title: "02-四重采集通道" created: 2026-04-24 tags: - 项目 aliases: - 第 2 讲:四重采集通道 --- # 四重采集通道 上一讲我们已经知道: > 爬虫的核心问题不是“怎么写代码”,而是先判断: > 数据到底在哪里? 这一讲就围绕这个问题展开。 同样是抓职位信息,数据可能出现在四个地方: ```text HTML 页面里 JavaScript 渲染后的页面里 接口 JSON 里 浏览器偷偷请求到的接口响应里 ``` 所以就对应四种采集通道: ```text 1. 静态 HTML 采集 2. 动态页面采集 3. API 直连采集 4. on_response 响应监听采集 ``` ## 1. 第一重:静态 HTML 采集 ### 1.1 它是什么? 静态 HTML 采集,就是: ```text 直接请求网页源码 ↓ 拿到 HTML ↓ 从 HTML 里提取数据 ``` 这是最传统、最基础的爬虫方式。 比如网页源码里直接有: ```html

数据分析师

某科技公司 15k-25k
``` 那我们就可以直接从 HTML 里提取: ```text 职位名称:数据分析师 公司名称:某科技公司 薪资:15k-25k ``` ### 1.2 生活化理解 你可以把它理解为: ```text 网页把菜单直接贴在墙上 你拍一张照片 然后从照片里抄菜名 ``` 数据已经在页面源码里了,不需要点击,不需要等待,不需要浏览器执行 JavaScript。 ### 1.3 最小代码长什么样? ```python 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) ``` 这段代码做了几件事: ```text requests.get(url) 请求网页 response.text 拿到 HTML 字符串 BeautifulSoup(html) 把 HTML 变成可查询对象 soup.select(".job") 找到所有职位块 select_one("h2") 找职位标题 get_text(strip=True) 提取干净文本 ``` #### 示例 ```python 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) ``` ![[2-Learning/05-项目/03-Python与爬虫/07-爬虫课程六讲/assets/image-b50e4cf1.png]] ![[2-Learning/05-项目/03-Python与爬虫/07-爬虫课程六讲/assets/image-34cff48c.png]] ### 1.4 它适合什么场景? 适合: ```text 学校就业网公告列表 简单企业官网招聘页 服务端直接渲染的列表页 不需要登录、不需要点击、不需要滚动的页面 ``` 比如很多学校就业网站,列表和详情内容直接在 HTML 里,这种就可以优先考虑静态 HTML 采集。 通用项目里的学校采集链路,本质上就是“列表页解析 + 明细页抓取”的两段式采集:先抽取列表容器 HTML,再进入详情页提取正文内容。 ### 1.5 优点和缺点 优点: ```text 速度快 代码简单 资源消耗小 不需要打开浏览器 适合大批量抓取 ``` 缺点: ```text 遇到 JavaScript 动态加载会抓不到数据 遇到复杂点击、滚动、分页不方便 页面结构一变,解析规则容易失效 ``` ## 2. 第二重:动态页面采集 ### 2.1 它是什么? 动态页面采集,就是用程序控制一个真实浏览器。 常见工具是: ```text Playwright Selenium ``` 我们现在重点看 Playwright,因为已有项目大量使用它。 动态页面采集流程是: ```text 启动浏览器 ↓ 打开网页 ↓ 等待页面加载 ↓ 点击筛选项 ↓ 滚动页面 ↓ 点击下一页或加载更多 ↓ 从渲染后的页面里提取数据 ``` ### 2.2 生活化理解 静态 HTML 采集像是“直接看纸质菜单”。 动态页面采集像是: ```text 你真的进了一家餐厅 点开菜单 点击分类 翻页 展开详情 再把内容抄下来 ``` 它更像真人操作浏览器。 ### 2.3 最小代码长什么样? ```python 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() ``` 这段代码对应的动作是: ```text launch() 启动浏览器 new_page() 打开新标签页 goto() 访问网页 click() 点击按钮 wait_for_selector() 等待某个元素出现 query_selector_all() 找到多个元素 inner_text() 提取页面文字 ``` #### 示例 ```python 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-Learning/05-项目/03-Python与爬虫/07-爬虫课程六讲/assets/PixPin_2026-04-24_17-53-45-899207ce.gif]] ### 2.4 它适合什么场景? 适合: ```text 必须点击筛选项才出现数据 必须滚动到底部才加载更多 页面是 Vue / React / Angular 渲染的 职位详情在弹窗或新 tab 中打开 需要模拟移动端页面 需要处理前端路由 ``` 比如企业招聘官网常见这些情况: ```text 点击城市 点击职位类别 点击加载更多 滚动页面加载下一批职位 详情页是前端路由 ``` 通用项目里的企业采集链路就增强了动态加载和多分页能力,包括自动滚动加载、加载更多按钮、分页函数和新开 tab 抓详情等设计。 ### 2.5 项目中的例子 猎聘项目中,脚本会用 Playwright 打开页面,并通过浏览器真实操作筛选项。比如选择企业职位、公司性质、公司规模、经验等,然后进入抓取流程。猎聘脚本不是直接拼 URL,而是通过页面控件驱动筛选,这就是典型的动态页面采集。 同时,猎聘脚本还会启动本地 Chromium、设置代理、关闭自动化特征、拦截图片/字体/媒体资源,目的是更像真实浏览器并降低资源消耗。 ### 2.6 优点和缺点 优点: ```text 适应复杂页面 可以点击、滚动、翻页 可以处理 JavaScript 渲染 接近真实用户行为 ``` 缺点: ```text 速度慢 资源消耗大 浏览器容易崩 并发成本高 更容易遇到反爬 选择器容易受页面改版影响 ``` 所以真实项目里一般不会无脑全用 Playwright。 能用接口就用接口;接口不好搞,再用 Playwright。 ## 3. 第三重:API 直连采集 ### 3.1 它是什么? API 直连采集,就是不抓网页,而是直接请求网站背后的数据接口。 比如网页上看到职位列表,其实浏览器背后可能请求了这个接口: ```text https://example.com/api/jobs?page=1&city=shanghai ``` 接口返回的不是 HTML,而是 JSON: ```json { "jobs": [ { "title": "数据分析师", "company": "某科技公司", "salary": "15k-25k", "city": "上海" } ] } ``` 这时候我们就可以直接请求接口。 ### 3.2 生活化理解 动态页面采集像是: ```text 去餐厅点菜,再从菜单上抄 ``` API 直连像是: ```text 直接找后厨要一份电子菜单表格 ``` 它更直接、更干净。 ### 3.3 最小代码长什么样? ```python 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) ``` 这段代码的重点是: ```text 不解析 HTML 不打开浏览器 直接解析 JSON ``` #### 示例 ```python 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() ``` ![[2-Learning/05-项目/03-Python与爬虫/07-爬虫课程六讲/assets/image-7e118cb6.png]] ### 3.4 它适合什么场景? 适合: ```text 接口容易找到 接口参数简单 接口不需要复杂签名 接口返回字段完整 接口稳定 ``` 比如很多企业招聘官网、部分校招官网、部分职位系统,其实都能找到职位接口。 通用项目里就有 `auto_api` 采集通道,原理是绕开前端渲染,直接请求官方职位接口,然后把接口数据映射成内部 `detail_*.json` 字段模型。 ### 3.5 怎么发现接口? 用浏览器开发者工具。 步骤大概是: ```text 打开网页 ↓ 按 F12 ↓ 进入 Network 面板 ↓ 刷新页面 ↓ 筛选 Fetch / XHR ↓ 点击请求 ↓ 看 Response 里有没有职位 JSON ``` 如果看到 Response 里有: ```json { "jobName": "产品经理", "workCity": "北京", "salary": "20k-30k" } ``` 那就说明这个接口可能可以用。 ### 3.6 优点和缺点 优点: ```text 最快 最干净 最省资源 字段结构清晰 不依赖页面样式 适合大规模采集 ``` 缺点: ```text 接口可能有签名 接口可能需要 Cookie 接口参数可能复杂 接口可能有频控 接口可能随版本变化 有些详情字段不在接口里 ``` 所以 API 直连是首选,但不是永远可行。 ## 4. 第四重:on\_response 响应监听采集 ### 4.1 它是什么? 这是非常实用的一种中间方案。 它的思路是: ```text 我不直接破解接口 也不只从 DOM 抠文字 我让浏览器正常打开页面 然后监听浏览器收到的接口响应 ``` 也就是说: ```text 页面自己去请求接口 我在旁边“偷听”接口返回的数据 ``` 这就是 `on_response`。 --- ### 4.2 生活化理解 API 直连像是你直接去问后厨: ```text 把电子菜单给我 ``` on\_response 像是: ```text 你坐在前台旁边 等服务员从后厨拿菜单出来 你顺手看一眼菜单内容 ``` 你不用自己知道后厨怎么下单,也不用完全破解接口参数。 ### 4.3 最小代码长什么样? ```python 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() ``` 关键代码是: ```text page.on("response", handle_response) ``` 意思是: ```text 只要页面收到任何网络响应 就交给 handle_response 处理 ``` 然后我们在里面判断: ```text if "/api/jobs" in response.url: ``` 意思是: ```text 只处理职位接口 其他图片、CSS、JS 都忽略 ``` --- ### 4.4 它适合什么场景? 适合: ```text 页面背后有接口 但接口参数复杂 接口有签名 接口需要浏览器环境 接口需要先点击筛选项才能触发 直接 requests 请求很麻烦 DOM 解析又不稳定 ``` 这类场景非常常见。 ### 4.5 项目中的例子 猎聘就是很典型的 on\_response 方案。 猎聘脚本通过页面真实操作触发搜索接口,然后监听猎聘搜索接口返回的数据,把 `jobCardList` 保存起来。之后再从页面 DOM 中拿职位跳转链接,通过职位 ID 去接口 JSON 里反查对应职位基础字段。这样比纯 DOM 抓列表更稳定。 猎聘公司版也监听公司职位条件接口和公司职位列表接口,用接口返回的条件集合生成抓取组合,而不是完全依赖页面上渲染出来的筛选项。 通用项目里也有 `auto_on_response` 通道,原理是在页面对象上注册 `page.on('response', handler)`,匹配目标接口 URL,然后提取响应 JSON 并落盘为内部结构。 ### 4.6 优点和缺点 优点: ```text 不用完全破解接口参数 比纯 DOM 抓取稳定 可以拿到结构化 JSON 适合复杂动态网站 能配合点击、筛选、滚动使用 ``` 缺点: ```text 仍然要启动浏览器 速度比 API 直连慢 需要判断哪些 response 是目标接口 接口响应时机不好控制 如果页面不触发接口,就监听不到 ``` ## 5. 四种采集通道怎么选? 可以用这个判断顺序: ```text 第一步:先看 HTML 源码里有没有数据 有 → 静态 HTML 采集 没有 → 看 Network 里有没有接口 JSON 有,而且接口好请求 → API 直连 有,但接口参数复杂 / 签名复杂 → on_response 监听 没有明显接口,必须点击/滚动/渲染后才有 → Playwright 动态页面采集 ``` 更直观一点: ```text 数据直接在 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. 用招聘网站举例 假设我们要抓一个招聘网站。 ### 场景一:学校就业网公告 页面源码里直接有: ```xml 某公司2026校园招聘 ``` 适合: ```text 静态 HTML 采集 ``` 流程: ```text 请求公告列表 ↓ 解析公告标题和链接 ↓ 请求详情页 ↓ 提取正文 ``` ### 场景二:企业官网职位页 页面打开后职位才慢慢出现,需要滚动或点加载更多。 适合: ```text 动态页面采集 ``` 流程: ```text 打开页面 ↓ 滚动到底部 ↓ 点击加载更多 ↓ 等职位卡片出现 ↓ 提取职位链接 ``` ### 场景三:企业官网职位接口 Network 里发现: ```text /api/recruit/jobs?page=1 ``` 返回 JSON。 适合: ```text API 直连采集 ``` 流程: ```text 请求接口 ↓ 解析 JSON ↓ 保存 detail_*.json ``` ### 场景四:猎聘这类复杂平台 页面需要真实操作筛选项,接口又有复杂参数。 适合: ```text 动态页面 + on_response 监听 ``` 流程: ```text 打开猎聘搜索页 ↓ 点击筛选条件 ↓ 监听职位搜索接口 ↓ 拿到职位列表 JSON ↓ 打开职位详情页补抓长文本 ↓ 保存并入库 ``` 这正是猎聘项目的核心路线:页面操作、接口监听、详情页补抓、落本地 JSON,再调用内部接口入库。 ## 8. 现在要建立的关键认知 不要把爬虫理解成“我会 requests”或者“我会 Playwright”。 真正的爬虫思维是: ```text 先判断数据在哪里 再选择采集通道 再决定解析方式 最后考虑稳定性和工程化 ``` 也就是: ```text 数据在 HTML? 用 HTML 解析。 数据在 JSON? 用接口解析。 接口不好直接请求? 用 response 监听。 页面必须交互? 用浏览器自动化。 数据在图片? 后面再加 OCR。 ``` ## 9. 这四种通道之间不是互斥的 真实项目里经常组合使用。 比如猎聘: ```text Playwright 打开页面 + 点击筛选条件 + on_response 监听列表接口 + DOM 抓详情页字段 + 本地 JSON 落盘 + 内部接口入库 ``` 这不是单一通道,而是组合拳。 再比如通用采集系统: ```text 有些站点走 DOM 有些站点走 API 有些站点走 on_response 后面统一进入 HTML 清洗、模型抽取、质控上传 ``` 它的设计思想就是“不押注单一抓取方式”,而是根据站点差异使用多通道采集冗余。 ## 10. 小结 ```text 数据在哪里? ↓ ┌────────────────┼────────────────┐ ↓ ↓ ↓ HTML JSON 渲染后页面 ↓ ↓ ↓ 静态 HTML 采集 API 直连采集 Playwright 动态采集 ↓ 接口复杂但页面能触发? ↓ on_response 监听 ``` > **爬虫不是固定用某一种技术,而是根据数据出现的位置选择采集通道。** --- **组内导航**:⬅️ [[01-爬虫是什么|爬虫是什么]] | 🏠 [[00-爬虫课程六讲|00-爬虫课程六讲]] | ➡️ [[03-爬虫的基础积木——每个动作能得到什么效果|爬虫的基础积木——每个动作能得到什么效果]]