匹配范围
*://sousuo.www.gov.cn/**://www.gov.cn/*gov-policy-search用途 读取中国政府网政策搜索结果页,返回指定关键词对应的政策搜索结果列表。 执行前提 先打开中国政府网政策搜索结果页,并确保页面 URL 中的 searchWord 与参数 query 一致,例如: https://sousuo.www.gov.cn/sousuo/search.shtml?code=17da70961a7&dataTypeId=107&s…
*://sousuo.www.gov.cn/**://www.gov.cn/*dom.read1return await (async () => {2 const input = typeof params === "object" && params !== null ? params : {};3 const query = cleanText(input.query);4 const limit = readLimit(input.limit, 10, 50);5 6 if (!query) {7 throw new Error("参数 query 必须是非空字符串。");8 }9 10 const searchUrl = buildSearchUrl(query);11 const currentUrl = new URL(location.href);12 const currentQuery = cleanText(13 currentUrl.searchParams.get("searchWord") ||14 (document.querySelector(".js_search_input") || {}).value15 );16 17 if (location.hostname !== "sousuo.www.gov.cn" && parseItems(document, limit).length === 0) {18 throw new Error("请先打开中国政府网搜索结果页后执行:" + searchUrl);19 }20 21 if (currentQuery && currentQuery !== query) {22 throw new Error("当前页面搜索词为“" + currentQuery + "”,请打开匹配 query 的搜索页:" + searchUrl);23 }24 25 const items = await waitForItems(limit, 8000);26 return {27 query,28 searchUrl,29 count: items.length,30 items,31 };32 33 function buildSearchUrl(searchWord) {34 const url = new URL("https://sousuo.www.gov.cn/sousuo/search.shtml");35 url.searchParams.set("code", "17da70961a7");36 url.searchParams.set("dataTypeId", "107");37 url.searchParams.set("searchWord", searchWord);38 return url.toString();39 }40 41 async function waitForItems(maxItems, timeoutMs) {42 const startedAt = Date.now();43 let latest = [];44 45 while (Date.now() - startedAt < timeoutMs) {46 latest = parseItems(document, maxItems);47 if (latest.length > 0 || isNoResultVisible()) {48 return latest;49 }50 await delay(250);51 }52 53 return parseItems(document, maxItems);54 }55 56 function parseItems(root, maxItems) {57 const nodes = Array.from(58 root.querySelectorAll(".basic_result_content .item, .js_basic_result_content .item")59 );60 const rows = [];61 const seen = new Set();62 63 for (const node of nodes) {64 const link =65 node.querySelector("a.title[href], a.guizhang-title[href], a.log-anchor[href]") ||66 node.querySelector("a[href]");67 if (!link) {68 continue;69 }70 71 const title = titleFromLink(link);72 const url = absoluteUrl(link.getAttribute("href"), document.baseURI);73 if (!title || !url) {74 continue;75 }76 77 const key = url + "\n" + title;78 if (seen.has(key)) {79 continue;80 }81 seen.add(key);82 83 const summaryNode = node.querySelector(84 ".description .detail, .description p, .detail, .line_clamp_two"85 );86 const sourceNode = node.querySelector(".source");87 const text = cleanText(node.textContent);88 rows.push({89 index: rows.length + 1,90 title,91 date: extractDate(text),92 source: sourceNode ? cleanText(sourceNode.textContent) : "",93 summary: summaryNode ? cleanText(summaryNode.textContent) : "",94 url,95 });96 97 if (rows.length >= maxItems) {98 break;99 }100 }101 102 return rows;103 }104 105 function titleFromLink(link) {106 const clone = link.cloneNode(true);107 clone.querySelectorAll(".type_title").forEach((node) => node.remove());108 return cleanText(link.getAttribute("title") || clone.textContent || link.textContent);109 }110 111 function isNoResultVisible() {112 const node = document.querySelector(".search_noResult, .js_search_noResult1");113 if (!node) {114 return false;115 }116 const style = getComputedStyle(node);117 return style.display !== "none" && style.visibility !== "hidden";118 }119 120 function delay(ms) {121 return new Promise((resolve) => setTimeout(resolve, ms));122 }123 124 function readLimit(value, fallback, max) {125 if (value === undefined || value === null || value === "") {126 return fallback;127 }128 const numberValue = Number(value);129 if (!Number.isInteger(numberValue)) {130 throw new Error("参数 limit 必须是整数。");131 }132 if (numberValue < 1 || numberValue > max) {133 throw new Error("参数 limit 必须在 1 到 " + max + " 之间。");134 }135 return numberValue;136 }137 138 function cleanText(value) {139 return String(value || "").replace(/\s+/g, " ").trim();140 }141 142 function absoluteUrl(value, baseUrl) {143 if (!value) {144 return "";145 }146 try {147 return new URL(String(value), baseUrl).toString();148 } catch (error) {149 return "";150 }151 }152 153 function extractDate(value) {154 const text = cleanText(value);155 const match =156 text.match(/\d{4}-\d{1,2}-\d{1,2}/) ||157 text.match(/\d{4}年\d{1,2}月\d{1,2}日/) ||158 text.match(/\d{4}[./]\d{1,2}[./]\d{1,2}/);159 return match ? match[0] : "";160 }161})();