Fetch one or more Scholar profile pages with lightweight fallbacks.
(scholar_url: str)
| 1261 | |
| 1262 | |
| 1263 | def _fetch_google_scholar_profile_pages(scholar_url: str) -> Dict[str, Any]: |
| 1264 | """Fetch one or more Scholar profile pages with lightweight fallbacks.""" |
| 1265 | timeout_seconds = max(5, _get_env_int("PAPERFLOW_SCHOLAR_TIMEOUT", SCHOLAR_TIMEOUT_SECONDS)) |
| 1266 | publication_limit = max(1, _get_env_int("PAPERFLOW_SCHOLAR_PUBLICATION_LIMIT", SCHOLAR_PUBLICATION_LIMIT)) |
| 1267 | page_size = max(20, _get_env_int("PAPERFLOW_SCHOLAR_PAGE_SIZE", 100)) |
| 1268 | max_pages = max(1, _get_env_int("PAPERFLOW_SCHOLAR_MAX_PAGES", SCHOLAR_MAX_PAGES)) |
| 1269 | |
| 1270 | session = requests.Session() |
| 1271 | errors: List[str] = [] |
| 1272 | profile: Dict[str, Any] = {} |
| 1273 | |
| 1274 | for page_index in range(max_pages): |
| 1275 | cstart = page_index * page_size |
| 1276 | if len(profile.get("all_publications", [])) >= publication_limit: |
| 1277 | break |
| 1278 | |
| 1279 | candidate_urls = [ |
| 1280 | _build_scholar_profile_url(scholar_url, cstart=cstart, pagesize=page_size, hl="en"), |
| 1281 | _build_scholar_profile_url(scholar_url, cstart=cstart, pagesize=20, hl="en"), |
| 1282 | _build_scholar_profile_url(scholar_url, cstart=cstart, pagesize=20, hl="zh-CN"), |
| 1283 | ] |
| 1284 | |
| 1285 | page_data: Dict[str, Any] = {} |
| 1286 | page_loaded = False |
| 1287 | for candidate_url in candidate_urls: |
| 1288 | try: |
| 1289 | response = session.get(candidate_url, headers=SCHOLAR_HEADERS, timeout=timeout_seconds) |
| 1290 | response.raise_for_status() |
| 1291 | parsed = _parse_google_scholar_profile_html(response.text) |
| 1292 | if parsed.get("blocked"): |
| 1293 | errors.append("Scholar returned an anti-bot / captcha page") |
| 1294 | continue |
| 1295 | if not parsed.get("interests") and not parsed.get("all_publications") and not parsed.get("publications"): |
| 1296 | fallback_excerpt = _build_scholar_fallback_excerpt(response.text) |
| 1297 | if fallback_excerpt: |
| 1298 | parsed["fallback_excerpt"] = fallback_excerpt |
| 1299 | errors.append("Scholar page loaded but structured profile signals were empty") |
| 1300 | continue |
| 1301 | page_data = parsed |
| 1302 | page_loaded = True |
| 1303 | break |
| 1304 | except requests.RequestException as exc: |
| 1305 | errors.append(f"{type(exc).__name__}: {exc}") |
| 1306 | |
| 1307 | if not page_loaded: |
| 1308 | if profile.get("all_publications") or profile.get("interests"): |
| 1309 | break |
| 1310 | raise ValueError("failed to fetch Google Scholar profile; " + "; ".join(errors[-3:])) |
| 1311 | |
| 1312 | profile = _merge_scholar_page_data(profile, page_data, publication_limit) |
| 1313 | |
| 1314 | current_page_publications = page_data.get("all_publications") or page_data.get("publications") or [] |
| 1315 | if len(current_page_publications) < page_size: |
| 1316 | break |
| 1317 | |
| 1318 | if not profile.get("interests") and not profile.get("publications"): |
| 1319 | raise ValueError("no scholar interests or publication rows found") |
| 1320 |
no test coverage detected