匹配范围
*://xueshu.baidu.com/*baidu-scholar-search百度学术搜索结果提取 用途 读取百度学术搜索结果页中已经渲染出的论文条目,返回标题、作者、期刊、年份、被引次数和结果地址。 执行前提 先打开 https://xueshu.baidu.com/s?wd=<query>&pn=0&tn=SEbaiduxueshuc1gjeupa 对应的搜索结果页;页面可能自动跳转到新版 https://xueshu.baid…
*://xueshu.baidu.com/*dom.read1const rawParams = params && typeof params === "object" ? params : {};2 3function integerParam(name, fallback, min, max) {4 const raw = rawParams[name];5 const value = raw === undefined || raw === null || raw === "" ? fallback : Number(raw);6 if (!Number.isInteger(value) || value < min || value > max) {7 throw new Error(`${name} must be an integer between ${min} and ${max}`);8 }9 return value;10}11 12function requiredQuery() {13 const value = rawParams.query === undefined || rawParams.query === null ? "" : String(rawParams.query).trim();14 if (!value) throw new Error("query is required");15 return value;16}17 18function sleep(ms) {19 return new Promise(resolve => setTimeout(resolve, ms));20}21 22function normalizeText(value) {23 return (value || "").replace(/\s+/g, " ").trim();24}25 26function expectedSearchUrl(query) {27 return `https://xueshu.baidu.com/s?wd=${encodeURIComponent(query)}&pn=0&tn=SE_baiduxueshu_c1gjeupa`;28}29 30function sameQuery(left, right) {31 return normalizeText(left).toLowerCase() === normalizeText(right).toLowerCase();32}33 34function assertCurrentSearchPage(query) {35 const current = new URL(location.href);36 const target = expectedSearchUrl(query);37 const allowedPaths = new Set(["/s", "/ndscholar/browse/search"]);38 if (current.hostname !== "xueshu.baidu.com" || !allowedPaths.has(current.pathname)) {39 throw new Error(`Open ${target} first, then run this script`);40 }41 const pageQuery = current.searchParams.get("wd") || "";42 if (!pageQuery || !sameQuery(pageQuery, query)) {43 throw new Error(`Open ${target} first, then run this script`);44 }45}46 47function detectBlockedPage(doc) {48 const text = normalizeText(`${doc.title || ""} ${doc.body ? doc.body.innerText : ""}`).slice(0, 5000);49 if (/百度安全验证|安全验证|验证码|人机验证|访问异常|页面加载失败|请输入验证码/.test(text)) {50 return "The page is showing a verification or risk-control challenge";51 }52 return "";53}54 55function trustedUrl(raw) {56 if (!raw) return "";57 try {58 const url = new URL(raw, "https://xueshu.baidu.com");59 if (url.protocol !== "http:" && url.protocol !== "https:") return "";60 return url.toString();61 } catch {62 return "";63 }64}65 66function parseScholarRow(el, rank) {67 const titleEl =68 el.querySelector("a.atomic-line-clamp-3[href]") ||69 el.querySelector("a[href*='/paper/show']") ||70 el.querySelector("h3 a") ||71 el.querySelector(".paper-title a") ||72 el.querySelector(".t a") ||73 el.querySelector("a[href]");74 const title = normalizeText(titleEl ? titleEl.textContent : "");75 if (!title) return null;76 77 const infoEl =78 el.querySelector(".paper-info") ||79 el.querySelector(".sc_info") ||80 el.querySelector(".res_info") ||81 el.querySelector(".info");82 const infoText = normalizeText(infoEl ? infoEl.textContent : el.textContent);83 const spans = infoEl ? Array.from(infoEl.querySelectorAll("span")) : [];84 const authorParts = [];85 const authorSeen = new Set();86 let journal = "";87 let year = "";88 let cited = null;89 90 function pushAuthorToken(value) {91 const text = normalizeText(value).replace(/[,,、]+$/g, "").trim();92 if (!text || text.length < 2) return;93 if (/^(期刊|会议|学位|图书|专利|来源|百度文库|万方|维普|掌桥科研|收藏|引用|免费下载|论文图谱|AI问答)$/.test(text)) return;94 if (/出版社$/.test(text)) return;95 if (/(\.\.\.|…)/.test(text)) return;96 if (/^[·,,。\-—…\s]+$/.test(text)) return;97 if (authorSeen.has(text)) return;98 authorSeen.add(text);99 authorParts.push(text);100 }101 102 for (const span of spans) {103 const text = normalizeText(span.textContent);104 if (!text || text === "," || text === "," || text === "-" || text === "期刊" || text === "会议" || text === "学位") continue;105 if (!journal && /^[《〈].+[》〉]$/.test(text)) {106 journal = text.replace(/[《》〈〉]/g, "");107 continue;108 }109 if (/被引/.test(text)) {110 const match = text.match(/(\d+)/);111 cited = match ? Number(match[1]) : cited;112 continue;113 }114 if (/(19|20)\d{2}/.test(text)) {115 const match = text.match(/(19|20)\d{2}/);116 year = match ? match[0] : year;117 continue;118 }119 if (!/^[-–—]/.test(text) && !/摘要|关键词|来源/.test(text)) {120 pushAuthorToken(text);121 }122 }123 124 if (!journal) {125 const journalMatch = infoText.match(/[《〈]([^《》〈〉]{2,80})[》〉]/);126 journal = journalMatch ? normalizeText(journalMatch[1]) : "";127 }128 if (!year) {129 const yearMatch = infoText.match(/(19|20)\d{2}/);130 year = yearMatch ? yearMatch[0] : "";131 }132 if (cited === null) {133 const citedMatch = infoText.match(/被引(?:量)?[::\s]*(\d+)/);134 cited = citedMatch ? Number(citedMatch[1]) : null;135 }136 137 return {138 rank,139 title,140 authors: authorParts.join(", ").slice(0, 120),141 journal,142 year,143 cited,144 url: trustedUrl(titleEl ? titleEl.getAttribute("href") || titleEl.href : ""),145 };146}147 148function extractResults(doc, limit) {149 const rows = [];150 const seen = new Set();151 const candidates = Array.from(doc.querySelectorAll(".result, .result-op"));152 153 for (const el of candidates) {154 if (rows.length >= limit) break;155 const row = parseScholarRow(el, rows.length + 1);156 if (!row) continue;157 const key = `${row.title}\n${row.url}`;158 if (seen.has(key)) continue;159 seen.add(key);160 rows.push(row);161 }162 163 return rows;164}165 166async function waitForResults(doc, limit) {167 const deadline = Date.now() + 8000;168 let results = extractResults(doc, limit);169 while (Date.now() < deadline && results.length === 0) {170 const blocked = detectBlockedPage(doc);171 if (blocked) throw new Error(`${blocked}; complete it in the browser and run again`);172 await sleep(250);173 results = extractResults(doc, limit);174 }175 return results;176}177 178const query = requiredQuery();179const limit = integerParam("limit", 10, 1, 20);180assertCurrentSearchPage(query);181 182const blocked = detectBlockedPage(document);183if (blocked) throw new Error(`${blocked}; complete it in the browser and run again`);184 185const items = await waitForResults(document, limit);186if (items.length === 0) {187 throw new Error(`No Baidu Scholar results found for "${query}"; the page may be empty, blocked, or its structure changed`);188}189 190return items.slice(0, limit);