项目来源:导师让我做一个,自动根据主题词,抓取相关热门文献的工作流,

主要功能:给到关键词,然后根据关键词自动抓取下载相关的问题
主要是可以通过工作流替代搜索论文,查找论文,在wos,谷歌学术等网站下载论文等工作。
项目背景:在学术研究与工程实践中,大规模获取与筛选文献是一项高频需求。然而,主流学术数据库(如 Web of Science、IEEE、Elsevier)部署了极为严苛的反爬机制(如 Cloudflare 盾、动态验证码、IP 频率限制)。传统的基于 HTTP 请求(如 requests、Scrapy)的爬虫方案在这些场景下几乎失效。
本项目实现了一套基于 Python 和 Playwright 的自动化文献检索与下载工作流(RPA)。其核心设计理念是"绝对禁止传统爬虫机制,纯粹模拟人类真实操作",通过接管本地已登录机构权限的浏览器配置,结合大模型(LLM)进行摘要的智能筛选,实现文献的自动化获取。
以下是该项目的技术架构与核心实现细节汇总。
一、 技术选型与项目架构
项目采用纯异步(Async)架构,主要依赖如下:
-
核心控制 :
Python 3.10+ -
网页交互 :
playwright>=1.49.0(异步 API) -
智能决策 :
openai>=1.55.0(兼容各类大语言模型 API) -
数据校验 :
pydantic>=2.9.0 -
终端 UI :
rich>=13.9.4(用于高亮与进度展示)
系统按功能解耦为多个独立模块,存放在 src/wos_rpa/ 下:
-
browser.py: 浏览器生命周期与持久化上下文管理。 -
config.py: 环境变量与基础配置加载。 -
human.py: 拟人化延迟控制。 -
scraper.py: 页面元素定位与数据抓取提取。 -
llm_filter.py: 大模型调用与相关性分析。 -
models.py: 基础数据结构定义。 -

二、 核心技术实现机制
1. 状态复用与浏览器持久化接管
为绕过 Web of Science 等平台的机构权限认证(如 WebVPN、EasyConnect 或 CARSI 登录),项目放弃了在干净的无头浏览器中处理复杂登录逻辑的做法,转而通过 launch_persistent_context 直接挂载用户日常使用的浏览器(Chrome 或 Edge)的数据目录(USER_DATA_DIR)。
-
持久化上下文:通过读取本地 Cookie 和 Session,脚本启动时自动继承用户的登录状态与机构访问权限。
-
死锁清理机制 :Playwright 在挂载本地用户目录时,常因浏览器异常退出残留
SingletonLock、SingletonCookie等文件而导致启动秒退(exitCode=21)。browser.py中实现了_clear_stale_singleton_locks函数,在启动前静默清理这些无效锁文件,并配合taskkill强制回收残留的msedge.exe或chrome.exe进程,大幅提高了脚本的启动成功率。
2. 拟人化操作控制
项目所有交互均严格遵循人类操作特征:
-
随机延迟 :底层封装了
human_sleep(min_s=2.0, max_s=5.0)函数,使用asyncio.sleep(random.uniform(min_s, max_s))确保在每次点击、输入、页面跳转之间注入 2 至 5 秒的随机停顿,避免触发高频操作风控。 -
真实视窗交互 :所有元素点击(如"展开摘要")均要求节点在视窗内可见,默认非无头模式(
HEADLESS=false)运行。
3. 高鲁棒性 DOM 定位策略
Web of Science 这类大型商业平台的 DOM 结构存在版本迭代或 A/B 测试。为了保证抓取的稳定性,scraper.py 采用了**多级选择器降级(Fallback)**策略。
例如,在匹配文献卡片、标题或摘要时,预定义了包含各种可能属性的元组:
TITLE_SELECTORS: tuple[str, ...] = (
'[data-ta="summary-record-title-link"]',
'a[href*="/full-record/"]',
'a[href*="full-record"]',
'a[href*="/record/"]',
'[data-ta*="title"] a',
"h3 a",
"h2 a",
"a.title",
)
系统会通过 _first_visible_locator 遍历这些选择器,遇到第一个可见且存在的节点即判定为命中。这种设计有效隔离了前端页面微调对自动化脚本的破坏。针对列表页抓取,实现了基于"卡片容器遍历"和"标题链接反查父节点(_row_from_title_link)"双轨抓取模式,最大限度避免漏抓。
4. 机构登录阻断与轮询机制
机构网络往往存在登录会话过期的情况。脚本在执行检索动作前,实现了一套阻塞式轮询机制(_wait_for_search_ready)。
系统通过 _maybe_handle_sign_in 扫描页面文本(如 "sign in to continue" 或 "institutional sign in"),判断是否遭遇登录墙。如果未发现检索框,脚本不会抛出异常退出,而是挂起并在终端提示用户进行手动干预,每隔一定时间(默认 5 秒)轮询检查检索框是否出现。一旦用户在弹出的浏览器中手动完成认证,脚本将自动恢复执行链路。
5. 大模型驱动的相关性过滤
传统的关键词检索会混入大量无关文献。项目在 llm_filter.py 中接入了兼容 OpenAI 格式的大模型 API,用于精确筛选。
-
系统将抓取到的
PaperItem(包含标题和摘要)连同用户定义的research_direction发送给 LLM。 -
通过
System Prompt约束 LLM 仅输出标准 JSON 格式:{"relevant": true/false, "reason": "简短理由"}。 -
利用
pydantic.BaseModel(RelevanceResult)对 LLM 的输出进行严格反序列化与字段校验。若模型输出不合规或触发JSONDecodeError,则抛出明确的异常以便上层逻辑处理重试。
三、 阶段性工作流运行
基于上述底层能力,项目的业务层按照逻辑阶段进行组装:
Phase 1: 冒烟测试 (phase1_wos_search_smoketest.py)
验证底层基础设施。启动持久化浏览器,处理可能的机构登录墙,找到输入框,输入指定的 keywords 并模拟回车触发检索。此阶段用于确认网络联通性及 USER_DATA_DIR 配置的正确性。
Phase 2: 提取与过滤 (phase2_extract_and_filter.py)
在 Phase 1 基础上,脚本等待检索结果列表加载完成(最大超时设置 90 秒),随后驱动浏览器逐个点击"View abstract/展开摘要"按钮。调用 scrape_current_results_page 收集当前页的所有文献元数据,并逐条调用 evaluate_paper_relevance_with_reason 让 LLM 进行打分,最终在终端渲染出被判定为相关的文献列表及理由。

