自动化文献检索与下载工作流 (Phase 3 逻辑树)
文档记录了当前工作流从启动到下载完成的端到端判断逻辑,按实际代码执行路径整理。
0. 启动入口
文件 : scripts/phase3_download_pipeline.py
- 读取
.env配置文件。 - 解析终端参数(
--keywords、--research-direction为必填项)。 - 初始化
settings(用户目录、下载目录、浏览器配置)。 - 调用
launch_persistent(settings)启动持久化浏览器。
1. 浏览器启动判定
文件 : src/wos_rpa/browser.py
- 1.1 启动 : 尝试执行
launch_persistent_context,使用USER_DATA_DIR+BROWSER_CHANNEL+BROWSER_EXECUTABLE。 - 1.2 异常处理 :
- 如果启动失败且命中
exitCode=21(Profile 锁冲突):- 自动执行
taskkill强制关闭残留的 Edge/Chrome 进程。 - 清理
Singleton*相关的锁文件。 - 重新尝试启动一次。
- 自动执行
- 如果启动失败且命中
- 1.3 彻底失败 : 如果重试仍然失败,抛出
RuntimeError,流程终止。
2. 进入 WoS 并搜索
- 2.1 访问主页 : 执行
page.goto(WOS basic-search)。- 成功则继续。
- 失败(如遇到
ERR_CONNECTION_RESET) -> 保存phase3_failure.png截图 -> 流程终止。
- 2.2 等待搜索框 (
_wait_for_search_ready) :- 检测到搜索框 -> 继续下一步。
- 未检测到搜索框:
- 识别是否处于登录页,并尝试点击
institution access。 - 每 5 秒进行一次轮询检测。
- 超过设定的最大等待时间 -> 报错结束。
- 识别是否处于登录页,并尝试点击
- 2.3 执行搜索 (
_do_search) :- 模拟输入关键词。
- 优先按下
Enter键回车。 - 发现 URL 未发生变化,则主动点击
Search按钮作为兜底。
3. 列表抓取
文件 : src/wos_rpa/scraper.py
- 3.1 等待结果加载 (
wait_for_results_loaded) :- 优先寻找"卡片容器" DOM 元素。
- 找不到则降级寻找"标题链接"元素。
- 都找不到且超时 -> 报错结束。
- 3.2 抓取模式选择 :
- 模式A : 若 卡片数 >= 标题链接数 -> 采用卡片驱动抓取。
- 模式B : 否则采用标题链接驱动抓取。
- 3.3 字段提取 (每条数据) :
- 提取
title(标题)、authors(作者)、abstract(摘要)。 - 提取
detail_url(详情页链接,内部逻辑过滤掉/author/等误抓链接)。 - 提取
year(利用正则匹配19xx/20xx,取出现的最大值)。
- 提取
4. LLM 相关性判定
文件 : src/wos_rpa/llm_filter.py
对每一篇抓取到
title或abstract的 paper 执行以下操作:
- 4.1 调用 API (
evaluate_paper_relevance_with_reason) :- 使用
OPENAI_API_KEY/OPENAI_BASE_URL/OPENAI_MODEL请求大模型。 - Prompt 强制要求返回 JSON 格式:
{"relevant": bool, "reason": str}。
- 使用
- 4.2 返回结果解析 :
relevant=False-> 直接跳过该论文。relevant=True-> 该文献进入最终下载流程。
- 4.3 API 失败分支 :
- 遇到
429频率限制或overloaded服务器过载 -> 触发指数退避自动重试。 - 返回 JSON 不合法 -> 抛错(注意:当前版本会直接中断整次 run 循环)。
- 遇到
5. 下载总策略
对每篇被 LLM 判定为
relevant=True的文献,执行"先 A 后 B"的降级下载策略:
A. 原站下载 (download_from_publisher)
- A1 : 打开论文的
detail_url。若打开失败 -> 返回None。 - A2 : 尝试点击 Publisher 入口(如 "Full Text at Publisher" 按钮)。若找不到 -> 返回
None。 - A3: 进入出版社独立页面(新 Tab 或同页),自动尝试点击接受 Cookies 的弹窗。
- A4 : 通过多选择器机制查找 PDF 下载按钮。
- 找到 : 触发
click_and_expect_download。- 成功: 文件保存至
downloads/目录,按照[year]-[first_author]-[title5].pdf格式重命名,结束当前论文处理。 - 失败: 拦截超时,进入兜底链 A5。
- 成功: 文件保存至
- 未找到: 直接进入兜底链 A5。
- 找到 : 触发
- A5 失败兜底链 :
- 保存当前页面的 Debug 截图(
download_debug_*)。 - Vision API 视觉点击 (若
ENABLE_VISION_CLICK=true):- 新规则 : 若视觉大模型明确回复
found=false-> 立即返回None(跳过处理下一篇)。 - 若
found=true且成功返回坐标触发下载 -> 成功并返回路径。
- 新规则 : 若视觉大模型明确回复
- OCR 点击 (若
ENABLE_OCR_CLICK=true):- 新规则: 检测 Tesseract 是否可用,不可用自动跳过不崩溃。若成功点击并下载 -> 返回路径。
- 人工介入 (若
MANUAL_INTERVENTION=true):- 程序挂起,等待用户手动点击网页上的下载按钮,程序在后台拦截下载流。
- 若以上全部失败 -> 返回
None,准备执行策略 B。
- 保存当前页面的 Debug 截图(
B. Scholar 代偿 (download_from_scholar,仅当 A 返回 None 时触发)
- B1 : 开启新 Tab 页进入 Google Scholar。
- 自动检测 CAPTCHA 验证码。如果触发,挂起等待用户手动完成验证并回车。
- B2 : 在学术搜索框内精确搜索
paper.title。- 再次检测 CAPTCHA 验证码(必要时再次等待人工完成)。
- B3 : 寻找搜索结果右侧的
[PDF]直链标签。- 找到 : 触发
expect_download进行直接下载。 - 找不到: 进入兜底链 B4。
- 找到 : 触发
- B4 失败兜底链 (逻辑同 A5) :
- 保存 Debug 截图。
- Vision API 视觉判定(
found=false直接跳过)。 - OCR 视觉点击。
- 人工点击介入。
- 最终彻底失败 -> 返回
None。
6. 结果输出
- 针对每一篇
relevant=True的文献,在终端实时输出状态:原站成功/原站失败转 Scholar/Scholar 成功/Scholar失败跳过
- 遍历结束后,打印最终统计报告:
相关文献通过数:N