您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Greasy Fork is available in English.
工业和信息化部政务服务平台,批量获取公司备案信息,手动查询一次公司名称后,批量获取该公司名下所有网站、APP、小程序、快应用备案内容,一键复制结果
// ==UserScript== // @name 工信部官网miit ICP备案批量查询小工具 // @namespace https://beian.miit.gov.cn // @version 0.6 // @description 工业和信息化部政务服务平台,批量获取公司备案信息,手动查询一次公司名称后,批量获取该公司名下所有网站、APP、小程序、快应用备案内容,一键复制结果 // @match *://beian.miit.gov.cn/* // @grant unsafeWindow // @grant GM_setClipboard // @run-at document-start // @author ejfkdev and Gemini 2.5 pro // @license MIT // ==/UserScript== (function() { 'use strict'; const TARGET_API_URL = 'https://hlwicpfwc.miit.gov.cn/icpproject_query/api/icpAbbreviateInfo/queryByCondition'; const UI_WRAPPER_ID = 'miit-userscript-ui-wrapper-v06'; // Version update in ID const FETCH_BUTTON_ID = 'miit-fetch-data-button-v06'; const PROGRESS_DIV_ID = 'miit-progress-div-v06'; const TEXTAREAS_TABLE_ID = 'miit-textareas-table-v06'; const UNIT_NAME_INPUT_SELECTOR = 'input.el-input__inner'; const TEXTAREA_CONFIG = [ { id: 'miit-ta-website-v06', labelText: '网站', serviceType: 1, dataField: 'domain', countSpanId: 'miit-count-website-v06', copyButtonId: 'miit-copy-website-v06' }, { id: 'miit-ta-app-v06', labelText: 'APP', serviceType: 6, dataField: 'serviceName', countSpanId: 'miit-count-app-v06', copyButtonId: 'miit-copy-app-v06' }, { id: 'miit-ta-mini_program-v06', labelText: '小程序', serviceType: 7, dataField: 'serviceName', countSpanId: 'miit-count-mini_program-v06', copyButtonId: 'miit-copy-mini_program-v06' }, { id: 'miit-ta-quick_app-v06', labelText: '快应用', serviceType: 8, dataField: 'serviceName', countSpanId: 'miit-count-quick_app-v06', copyButtonId: 'miit-copy-quick_app-v06' } ]; const REQUIRED_MONITORED_HEADERS = ['sign', 'token', 'uuid']; let monitoredRequestData = { sign: null, token: null, uuid: null }; let uiInjected = false; let isFetchingAllData = false; function delay(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } function headersToObject(headersSource) { const obj = {}; if (headersSource instanceof Headers) { headersSource.forEach((value, key) => { obj[key.toLowerCase()] = value; }); } else if (Array.isArray(headersSource)) { headersSource.forEach(([key, value]) => { obj[key.toLowerCase()] = value; }); } else if (typeof headersSource === 'object' && headersSource !== null) { for (const key in headersSource) { obj[key.toLowerCase()] = headersSource[key]; } } return obj; } async function fetchDataForServiceType(serviceConfig, unitName, baseApiHeaders, progressDivElement, textareaElement, labelCountSpanElement) { const collectedItems = new Set(); let currentPage = 1; let totalPages = 1; const pageSize = "40"; textareaElement.value = ""; if (labelCountSpanElement) labelCountSpanElement.textContent = " (0)"; progressDivElement.textContent = `进度: 即将开始获取 ${serviceConfig.labelText} 数据...`; while (currentPage <= totalPages && isFetchingAllData) { const payloadForPage = { pageNum: String(currentPage), pageSize: pageSize, unitName: unitName, serviceType: serviceConfig.serviceType }; const currentHeaders = { ...baseApiHeaders, 'sign': monitoredRequestData.sign, 'token': monitoredRequestData.token, 'uuid': monitoredRequestData.uuid, 'content-type': 'application/json' }; const browserControlledHeaders = ['host', 'connection', 'content-length', 'cookie']; Object.keys(currentHeaders).forEach(key => { const lowerKey = key.toLowerCase(); if (browserControlledHeaders.includes(lowerKey) || (lowerKey.startsWith('sec-') && !['sec-ch-ua', 'sec-ch-ua-mobile', 'sec-ch-ua-platform', 'sec-fetch-dest', 'sec-fetch-mode', 'sec-fetch-site', 'sec-gpc'].includes(lowerKey) )) { if (!['user-agent', 'origin', 'referer'].includes(lowerKey) || !baseApiHeaders[lowerKey]) { delete currentHeaders[key]; } } }); progressDivElement.textContent = `进度: 正在获取 ${serviceConfig.labelText} 数据 (第 ${currentPage}${totalPages > 1 && currentPage > 1 ? '/'+totalPages : ''} 页)...`; console.log(`[MIIT Helper] Fetching ${serviceConfig.labelText} - Page ${currentPage} - Payload:`, payloadForPage); try { const response = await unsafeWindow.fetch(TARGET_API_URL, { method: 'POST', headers: currentHeaders, body: JSON.stringify(payloadForPage), mode: 'cors', credentials: 'include', referrer: currentHeaders['referer'] || 'https://beian.miit.gov.cn/', referrerPolicy: 'strict-origin-when-cross-origin' }); if (!response.ok) { let errorText = `HTTP错误! 状态: ${response.status} ${response.statusText}`; try { const errorData = await response.text(); errorText += `, 响应: ${errorData.substring(0,200)}`; } catch (e) {} throw new Error(errorText); } const responseData = await response.json(); if (responseData.success && responseData.code === 200) { const params = responseData.params || {}; if (currentPage === 1) { totalPages = parseInt(params.pages, 10) || 1; console.log(`[MIIT Helper] ${serviceConfig.labelText} - 总页数: ${totalPages}`); if (totalPages === 0 && params.list && params.list.length > 0) totalPages = 1; if (totalPages === 0 || (!params.list || params.list.length === 0 && totalPages <=1) ) { if (params.list && params.list.length > 0) { /* continue */ } else { progressDivElement.textContent = `进度: ${serviceConfig.labelText} - 服务器报告0页或无数据。`; if ((!params.list || params.list.length === 0)) break; } } } const itemListOnPage = params.list || []; let pageItemsCount = 0; for (const item of itemListOnPage) { const valueToCollect = item[serviceConfig.dataField]; if (valueToCollect) { collectedItems.add(valueToCollect); pageItemsCount++; } } console.log(`[MIIT Helper] 从第 ${currentPage} 页 (${serviceConfig.labelText}) 提取到 ${pageItemsCount} 个条目。`); textareaElement.value = Array.from(collectedItems).sort().join('\n'); if (labelCountSpanElement) labelCountSpanElement.textContent = ` (${collectedItems.size})`; if (currentPage >= totalPages) break; currentPage++; } else { const errMsg = `[MIIT Helper] 第 ${currentPage} 页 (${serviceConfig.labelText}) API错误: ${responseData.msg || '未知API错误'}`; progressDivElement.textContent = `进度: ${errMsg}`; console.error(errMsg, responseData); throw new Error(errMsg); } } catch (e) { const errMsg = `[MIIT Helper] 请求第 ${currentPage} 页 (${serviceConfig.labelText}) 时出错: ${e.message ? e.message.substring(0,150) : String(e).substring(0,150)}`; progressDivElement.textContent = `错误: ${errMsg}`; console.error(errMsg, e); isFetchingAllData = false; const fetchButton = document.getElementById(FETCH_BUTTON_ID); if (fetchButton) { fetchButton.disabled = !(monitoredRequestData.sign && monitoredRequestData.token && monitoredRequestData.uuid); fetchButton.style.backgroundColor = fetchButton.disabled ? '#aaa' : '#4CAF50'; // 更新按钮颜色 } throw e; } if (currentPage <= totalPages && isFetchingAllData) { // 只有当还有下一页且未被中断时才延迟 await delay(10000); // 每个分页请求间隔调整到10秒 } } console.log(`[MIIT Helper] ${serviceConfig.labelText} 数据提取完成,共 ${collectedItems.size} 条。`); } async function handleFetchAllData() { if (isFetchingAllData) { alert("已有一个获取任务正在进行中,请稍候..."); return; } isFetchingAllData = true; const fetchButton = document.getElementById(FETCH_BUTTON_ID); if(fetchButton) fetchButton.disabled = true; const progressDiv = document.getElementById(PROGRESS_DIV_ID); const unitNameInputElement = document.querySelector(UNIT_NAME_INPUT_SELECTOR); if (!unitNameInputElement || !unitNameInputElement.value.trim()) { const msg = "错误: 请在页面顶部的输入框中输入单位名称 (例如 科大讯飞股份有限公司)。"; if (progressDiv) progressDiv.textContent = msg; console.error(`[MIIT Helper] ${msg}`); alert(msg); isFetchingAllData = false; if(fetchButton) { fetchButton.disabled = false; // 出错,重新启用按钮 (如果头部已捕获) fetchButton.style.backgroundColor = (monitoredRequestData.sign && monitoredRequestData.token && monitoredRequestData.uuid) ? '#4CAF50' : '#aaa'; } return; } const unitName = unitNameInputElement.value.trim(); if (!monitoredRequestData.sign || !monitoredRequestData.token || !monitoredRequestData.uuid) { const msg = "错误: 必需的请求头 (sign, token, uuid) 尚未从页面请求中捕获。请先在页面上进行一次查询(可能需要验证码)以捕获这些信息。"; if (progressDiv) progressDiv.textContent = msg; console.error(`[MIIT Helper] ${msg}`); alert(msg); isFetchingAllData = false; // 按钮状态由 updateUIAfterHeadersCaptured 控制,这里不直接启用 return; } console.log(`[MIIT Helper] '获取备案数据' 按钮被点击。单位名称: ${unitName}`); if (progressDiv) progressDiv.textContent = '进度: 初始化中...'; TEXTAREA_CONFIG.forEach(cfg => { const ta = document.getElementById(cfg.id); if (ta) ta.value = ""; const lbl = document.getElementById(cfg.countSpanId); if (lbl) lbl.textContent = " (0)"; }); const baseApiHeadersFromExample = { "accept": "application/json, text/plain, */*", "accept-language": "zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7", "cache-control": "no-cache", "pragma": "no-cache", "origin": "https://beian.miit.gov.cn", "referer": "https://beian.miit.gov.cn/" }; baseApiHeadersFromExample['user-agent'] = navigator.userAgent; await delay(3000); for (let i = 0; i < TEXTAREA_CONFIG.length; i++) { if (!isFetchingAllData) break; // 如果中途出错,则不再继续 const serviceConfig = TEXTAREA_CONFIG[i]; const textareaElement = document.getElementById(serviceConfig.id); const labelCountSpanElement = document.getElementById(serviceConfig.countSpanId); try { await fetchDataForServiceType(serviceConfig, unitName, baseApiHeadersFromExample, progressDiv, textareaElement, labelCountSpanElement); if (i < TEXTAREA_CONFIG.length - 1 && isFetchingAllData) { progressDiv.textContent = `进度: ${serviceConfig.labelText} 完成。等待10秒后获取下一组...`; await delay(10000); } } catch (error) { console.error(`[MIIT Helper] 获取 ${serviceConfig.labelText} 数据时发生严重错误,后续任务已中止。`, error); // isFetchingAllData 和按钮状态已在 fetchDataForServiceType 中处理 break; } } if (isFetchingAllData) { if (progressDiv) progressDiv.textContent = '进度: 所有数据类型获取完毕!(已去重)'; } isFetchingAllData = false; if(fetchButton) { fetchButton.disabled = !(monitoredRequestData.sign && monitoredRequestData.token && monitoredRequestData.uuid); fetchButton.style.backgroundColor = fetchButton.disabled ? '#aaa' : '#4CAF50'; } } function createCopyButton(textareaId, buttonText = "复制内容") { const button = document.createElement('button'); button.textContent = buttonText; button.setAttribute('style', 'margin-top: 5px; padding: 3px 8px; font-size: 11px; cursor: pointer; background-color: #f0f0f0; border: 1px solid #ccc; border-radius: 3px;'); button.onmouseover = function() { this.style.backgroundColor = '#e0e0e0'; }; button.onmouseout = function() { this.style.backgroundColor = '#f0f0f0'; }; button.onclick = function() { const textarea = document.getElementById(textareaId); if (textarea && textarea.value) { GM_setClipboard(textarea.value, 'text'); const originalText = this.textContent; this.textContent = '已复制!'; this.disabled = true; setTimeout(() => { this.textContent = originalText; this.disabled = false; }, 2000); } else { alert("没有内容可复制。"); } }; return button; } function tryInjectUI() { if (document.getElementById(UI_WRAPPER_ID)) { updateUIAfterHeadersCaptured(); // 如果UI已存在,仅更新其状态 return; } const targetContainer = document.querySelector('div.listcont'); if (!targetContainer) { setTimeout(tryInjectUI, 2000); return; } console.log("[MIIT Helper] 正在注入初始UI..."); const wrapperDiv = document.createElement('div'); wrapperDiv.id = UI_WRAPPER_ID; wrapperDiv.setAttribute('style', 'border: 1px solid #ccc; padding: 15px; margin-top: 20px; margin-bottom: 20px; background-color: #f9f9f9; font-family: sans-serif;'); const button = document.createElement('button'); button.id = FETCH_BUTTON_ID; button.textContent = '批量获取所有备案数据'; // 初始样式为禁用 button.setAttribute('style', 'padding: 10px 18px; font-size: 16px; cursor: not-allowed; background-color: #aaa; color: white; border: none; border-radius: 4px; transition: background-color 0.3s ease;'); button.disabled = true; button.onclick = handleFetchAllData; wrapperDiv.appendChild(button); const progressDiv = document.createElement('div'); progressDiv.id = PROGRESS_DIV_ID; progressDiv.textContent = '提示: 请先在页面顶部的输入框进行一次查询(可能需要输入验证码),本工具捕获到必要信息后,上方按钮将启用。'; progressDiv.setAttribute('style', 'margin-top: 10px; margin-bottom:10px; min-height: 1.2em; font-weight: bold; color: #777;'); wrapperDiv.appendChild(progressDiv); const table = document.createElement('table'); table.id = TEXTAREAS_TABLE_ID; table.setAttribute('style', 'width: 100%; margin-top: 10px; border-collapse: collapse; display: none;'); const trLabels = table.insertRow(); TEXTAREA_CONFIG.forEach(cfg => { const th = document.createElement('th'); th.setAttribute('style', 'width: 25%; text-align: left; padding: 8px; border: 1px solid #ddd; background-color: #f0f0f0; font-size: 14px;'); th.textContent = cfg.labelText; const countSpan = document.createElement('span'); countSpan.id = cfg.countSpanId; countSpan.textContent = " (0)"; countSpan.style.fontWeight = 'normal'; countSpan.style.fontSize = '12px'; th.appendChild(countSpan); trLabels.appendChild(th); }); const trTextareas = table.insertRow(); TEXTAREA_CONFIG.forEach(cfg => { const td = trTextareas.insertCell(); td.setAttribute('style', 'width: 25%; padding: 5px; border: 1px solid #ddd; vertical-align: top; text-align: center;'); const textarea = document.createElement('textarea'); textarea.id = cfg.id; textarea.setAttribute('style', 'width: 98%; height: 200px; box-sizing: border-box; font-size: 12px; padding: 5px; border: 1px solid #ccc; margin-bottom: 5px;'); textarea.readOnly = true; td.appendChild(textarea); td.appendChild(createCopyButton(cfg.id, `复制 ${cfg.labelText}`)); }); wrapperDiv.appendChild(table); if (targetContainer.firstChild) { targetContainer.insertBefore(wrapperDiv, targetContainer.firstChild); } else { targetContainer.appendChild(wrapperDiv); } uiInjected = true; console.log("[MIIT Helper] UI框架注入成功。"); updateUIAfterHeadersCaptured(); // 根据当前是否有头部信息更新按钮状态 } function updateUIAfterHeadersCaptured() { if (!uiInjected) { // 如果UI还未注入,先尝试注入 // 在监控到头部后,如果页面已加载,可以尝试注入 if (document.readyState === "complete" || document.readyState === "interactive") { tryInjectUI(); } else { // 如果DOM未加载完,监听事件稍后注入。这确保 targetContainer 存在。 window.addEventListener('DOMContentLoaded', tryInjectUI, { once: true }); } return; // tryInjectUI 内部会再次调用此函数来更新状态 } const fetchButton = document.getElementById(FETCH_BUTTON_ID); const textareasTable = document.getElementById(TEXTAREAS_TABLE_ID); const progressDiv = document.getElementById(PROGRESS_DIV_ID); if (fetchButton && textareasTable && progressDiv) { const headersNowAvailable = monitoredRequestData.sign && monitoredRequestData.token && monitoredRequestData.uuid; if (headersNowAvailable) { fetchButton.disabled = false; fetchButton.style.backgroundColor = '#4CAF50'; // 绿色,表示可用 fetchButton.style.cursor = 'pointer'; textareasTable.style.display = ''; // 显示表格 if (progressDiv.textContent.includes("请先在上方输入框搜索")) { progressDiv.textContent = "提示: 必要信息已捕获!可点击上方按钮获取数据。"; progressDiv.style.color = '#333'; // 正常颜色 } } else { fetchButton.disabled = true; fetchButton.style.backgroundColor = '#aaa'; // 灰色,表示禁用 fetchButton.style.cursor = 'not-allowed'; textareasTable.style.display = 'none'; // 隐藏表格 progressDiv.textContent = '提示: 请先在页面顶部的输入框进行一次查询(可能需要输入验证码),本工具捕获到必要信息后,上方按钮将启用。'; progressDiv.style.color = '#777'; } } } // Monkey patch window.fetch const originalFetch = unsafeWindow.fetch; unsafeWindow.fetch = async function(...args) { const resource = args[0]; const config = args[1]; let requestUrl = (typeof resource === 'string') ? resource : resource.url; let responseClone; // 用于安全地读取响应体而不消耗它 let result; // 先执行原始请求 try { result = await originalFetch.apply(this, args); if (requestUrl === TARGET_API_URL) { responseClone = result.clone(); // 克隆响应对象以便安全地读取body } } catch(err) { console.error("[MIIT Monitor (fetch)] Original fetch error:", err); throw err; // 重新抛出原始错误 } if (requestUrl === TARGET_API_URL) { console.log(`[MIIT Monitor (fetch)] 捕获到页面自身对目标API的请求: ${requestUrl}`); const requestConfigHeaders = headersToObject(config && config.headers ? config.headers : (resource instanceof Request ? resource.headers : {})); let headersChangedOrNewlyFound = false; REQUIRED_MONITORED_HEADERS.forEach(headerName => { const headerValue = requestConfigHeaders[headerName.toLowerCase()]; if (headerValue) { if (monitoredRequestData[headerName] !== headerValue) { console.log(`[MIIT Monitor (fetch)] 更新 ${headerName}: ${headerValue.substring(0,30)}...`); monitoredRequestData[headerName] = headerValue; headersChangedOrNewlyFound = true; } } }); if (headersChangedOrNewlyFound || (monitoredRequestData.sign && !uiInjected)) { updateUIAfterHeadersCaptured(); } // 调试:可以打印克隆的响应体 // responseClone.text().then(text => console.log('[MIIT Monitor (fetch)] Cloned Response body for target URL:', text.substring(0, 200))); } return result; }; // Monkey patch XMLHttpRequest const originalXhrOpen = unsafeWindow.XMLHttpRequest.prototype.open; const originalXhrSetRequestHeader = unsafeWindow.XMLHttpRequest.prototype.setRequestHeader; const originalXhrSend = unsafeWindow.XMLHttpRequest.prototype.send; const xhrRequestDataMap = new WeakMap(); unsafeWindow.XMLHttpRequest.prototype.open = function(method, url, ...restArgs) { if (String(url) === TARGET_API_URL) { xhrRequestDataMap.set(this, { url: url, headers: {}, method: method, processedOnSend: false }); } else { if (xhrRequestDataMap.has(this)) xhrRequestDataMap.delete(this); } return originalXhrOpen.apply(this, [method, url, ...restArgs]); }; unsafeWindow.XMLHttpRequest.prototype.setRequestHeader = function(header, value) { const requestData = xhrRequestDataMap.get(this); if (requestData) { requestData.headers[header.toLowerCase()] = value; } return originalXhrSetRequestHeader.apply(this, [header, value]); }; unsafeWindow.XMLHttpRequest.prototype.send = function(...sendArgs) { const requestData = xhrRequestDataMap.get(this); if (requestData && requestData.url === TARGET_API_URL && !requestData.processedOnSend) { requestData.processedOnSend = true; console.log(`[MIIT Monitor (XHR)] 捕获到页面自身对目标API的 ${requestData.method} 请求: ${requestData.url}`); let headersChangedOrNewlyFound = false; REQUIRED_MONITORED_HEADERS.forEach(headerName => { const headerValue = requestData.headers[headerName.toLowerCase()]; if (headerValue) { if (monitoredRequestData[headerName] !== headerValue) { console.log(`[MIIT Monitor (XHR)] 更新 ${headerName}: ${headerValue.substring(0,30)}...`); monitoredRequestData[headerName] = headerValue; headersChangedOrNewlyFound = true; } } }); if (headersChangedOrNewlyFound || (monitoredRequestData.sign && !uiInjected)) { updateUIAfterHeadersCaptured(); } } return originalXhrSend.apply(this, sendArgs); }; console.log('[MIIT Helper] Userscript v0.6 已加载。'); window.addEventListener('DOMContentLoaded', () => { tryInjectUI(); // 在DOM加载后立即尝试注入初始UI }, { once: true }); })();