Files
post_crawler/docs/crawler_design.md
T
2026-05-26 21:02:17 +08:00

357 lines
8.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Crawler 模块设计方案
## 状态
✅ 已完成
---
## 一、输入参数
| 参数 | 来源 | 说明 |
|-----|------|-----|
| url | 初始输入 | 列表页 URL |
| job_item_selector | Step 2 | 岗位项选择器 |
| item_change_type | Step 2 | redirect / new_tab / in_page |
| next_page_selector | Step 3 | 下一页按钮选择器(None = 无分页) |
| page_change_type | Step 3 | url_change / content_change / new_tab |
| field_selectors | Step 4 | 字段选择器字典,selector 为数组格式 |
| detail_area_selector | Step 4 | 详情区域选择器(仅 in_page 有值) |
---
## 二、限制条件
- 最大页数:5 页
- 单个岗位失败:跳过继续
- 超时时间:可配置(如 10 秒)
---
## 三、数据提取范围
| item_change_type | 提取范围 | detail_url 记录 |
|-----------------|---------|---------------|
| redirect | 跳转后的详情页整页 | 详情页 URL |
| new_tab | 新标签详情页整页 | 新标签页 URL |
| in_page | detail_area_selector 内部 | 列表页 URL |
---
## 四、入口逻辑
```
results = []
IF item_change_type == "redirect":
results = 流程A_redirect()
ELSE IF item_change_type == "new_tab":
results = 流程B_new_tab()
ELSE IF item_change_type == "in_page":
results = 流程C_in_page()
RETURN results
```
---
## 五、流程 Aredirect
**特点**:点击岗位后整页跳转,返回后状态丢失,需重新打开并翻页恢复
```
初始化:
page_index = 1
item_index = 1
results = []
打开 url
等待加载完成
items_per_page = 用 job_item_selector 计算当前页岗位数
主循环:
WHILE True:
# ===== 1. 检查是否结束 =====
IF items_per_page == 0:
BREAK
IF page_index > 5:
BREAK
# ===== 2. 处理当前岗位 =====
TRY:
elements = 获取所有岗位元素(job_item_selector)
点击 elements[item_index - 1]
等待页面跳转完成(URL 变化 或 networkidle
data = 在整页范围用 field_selectors 提取数据
data["detail_url"] = 当前页面 URL
results.append(data)
CATCH:
记录失败,跳过
# ===== 3. 计算下一个位置 =====
item_index += 1
IF item_index > items_per_page:
page_index += 1
item_index = 1
IF page_index > 5:
BREAK
IF next_page_selector == None:
BREAK
# ===== 4. 重新打开并恢复到目标页 =====
goto(url)
等待加载完成
FOR i = 1 TO (page_index - 1):
success, page = click_next_page(page)
IF not success:
BREAK 主循环
items_per_page = 重新计算当前页岗位数
RETURN results
```
---
## 六、流程 Bnew_tab
**特点**:点击后新标签打开详情,关闭新标签后原页面状态保持
```
初始化:
page_index = 1
results = []
打开 url
等待加载完成
主循环:
WHILE page_index <= 5:
# ===== 1. 获取当前页岗位 =====
items_per_page = 用 job_item_selector 计算当前页岗位数
IF items_per_page == 0:
BREAK
# ===== 2. 遍历当前页所有岗位 =====
FOR item_index = 1 TO items_per_page:
TRY:
elements = 获取所有岗位元素(job_item_selector)
开始监听 context 的 "page" 事件
点击 elements[item_index - 1]
等待新标签页打开(超时则跳过)
new_page = 获取新打开的标签页
等待 new_page 加载完成
data = 在 new_page 整页范围用 field_selectors 提取数据
data["detail_url"] = new_page.url
results.append(data)
关闭 new_page
CATCH:
尝试关闭可能存在的新标签
记录失败,跳过
# ===== 3. 翻页 =====
IF next_page_selector == None:
BREAK
success, page = click_next_page(page)
IF not success:
BREAK
page_index += 1
RETURN results
```
---
## 七、流程 Cin_page
**特点**:点击后弹窗/详情区展示,通过刷新页面恢复状态,兼容性更强
```
初始化:
page_index = 1
item_index = 1
results = []
打开 url
等待加载完成
items_per_page = 用 job_item_selector 计算当前页岗位数
主循环:
WHILE True:
# ===== 1. 检查是否结束 =====
IF items_per_page == 0:
BREAK
IF page_index > 5:
BREAK
# ===== 2. 处理当前岗位 =====
TRY:
elements = 获取所有岗位元素(job_item_selector)
点击 elements[item_index - 1]
等待 detail_area_selector 出现(visible
detail_element = 获取 detail_area_selector 元素
data = 在 detail_element 内部用 field_selectors 提取数据
data["detail_url"] = 当前页面 URL
results.append(data)
CATCH:
记录失败,跳过
# ===== 3. 计算下一个位置 =====
item_index += 1
IF item_index > items_per_page:
page_index += 1
item_index = 1
IF page_index > 5:
BREAK
IF next_page_selector == None:
BREAK
# ===== 4. 刷新页面恢复状态 =====
goto(url)
等待加载完成
FOR i = 1 TO (page_index - 1):
success, page = click_next_page(page)
IF not success:
BREAK 主循环
items_per_page = 重新计算当前页岗位数
RETURN results
```
---
## 八、统一翻页函数
**返回值**(success: bool, page: Page) —— new_tab 模式时 page 引用会变
```
函数 click_next_page(page, next_page_selector, page_change_type):
# ===== 1. 检查按钮是否可点 =====
element = 查找 next_page_selector
IF element 不存在:
RETURN (false, page)
IF element 有 disabled 属性:
RETURN (false, page)
IF element 的 class 包含 "disabled":
RETURN (false, page)
IF element 不可见 (is_visible == false):
RETURN (false, page)
# ===== 2. 根据 page_change_type 处理 =====
IF page_change_type == "url_change":
before_url = page.url
点击 element
等待 URL 变化(page.url != before_url
等待加载完成(networkidle
RETURN (true, page)
ELSE IF page_change_type == "content_change":
before_text = 获取第一个岗位的文本内容
点击 element
等待第一个岗位文本变化(!= before_text
短暂等待确保渲染完成
RETURN (true, page)
ELSE IF page_change_type == "new_tab":
开始监听 context 的 "page" 事件
点击 element
等待新标签页打开
new_page = 获取新标签页
等待 new_page 加载完成
关闭原 page
RETURN (true, new_page) # 返回新的 page 引用
```
---
## 九、数据提取函数
```
函数 extract_data(scope, field_selectors):
# scope: 整个 page 或 detail_element
data = {}
FOR field_name, selector_info IN field_selectors:
TRY:
selectors = selector_info.selector # 数组格式
IF selectors 为空数组:
data[field_name] = None
CONTINUE
# 遍历所有选择器,提取文本并拼接
texts = []
FOR selector IN selectors:
element = scope.query_selector(selector)
IF element:
text = element.inner_text().strip()
IF text:
texts.append(text)
data[field_name] = "\n".join(texts) IF texts ELSE None
CATCH:
data[field_name] = None
RETURN data
```
---
## 十、字段选择器格式
`field_selectors` 统一使用数组格式:
```
{
"job_title": {"selector": ["h1.title"], "sample": "Python工程师"},
"description": {"selector": [".desc", ".req"], "sample": "负责...\n要求..."},
"salary": {"selector": [".salary"], "sample": "25-40K"},
...
}
```
- 单选择器:`["h1.title"]`
- 多选择器:`[".desc", ".req"]`,提取结果用 `\n` 拼接
---
## 十一、输出格式
```
[
{
"job_title": "...",
"salary": "...",
"location": "...",
"description": "...",
"requirements": "...",
"detail_url": "...",
...
},
...
]
```