// ==UserScript== // @name 贝壳房源信息收集器 (成交/在售双模式) // @namespace http://tampermonkey.net/ // @version 2.1 // @description 在浏览贝壳(ke.com)时,自动收集成交列表页和在售详情页的房源信息,并提供独立的、动态命名的CSV下载功能。 // @author CodeDust // @match https://*.ke.com/chengjiao/* // @match https://*.ke.com/ershoufang/*.html* // @grant GM_setValue // @grant GM_getValue // @grant GM_addStyle // @license MIT // @downloadURL https://update.greasyfork.icu/scripts/542839/%E8%B4%9D%E5%A3%B3%E6%88%BF%E6%BA%90%E4%BF%A1%E6%81%AF%E6%94%B6%E9%9B%86%E5%99%A8%20%28%E6%88%90%E4%BA%A4%E5%9C%A8%E5%94%AE%E5%8F%8C%E6%A8%A1%E5%BC%8F%29.user.js // @updateURL https://update.greasyfork.icu/scripts/542839/%E8%B4%9D%E5%A3%B3%E6%88%BF%E6%BA%90%E4%BF%A1%E6%81%AF%E6%94%B6%E9%9B%86%E5%99%A8%20%28%E6%88%90%E4%BA%A4%E5%9C%A8%E5%94%AE%E5%8F%8C%E6%A8%A1%E5%BC%8F%29.meta.js // ==/UserScript== (function() { 'use strict'; // --- 全局配置 --- const STORAGE_KEYS = { CHENGJIAO: 'beike_chengjiao_data', ERSHOUFANG: 'beike_ershoufang_data' }; /** * 主函数,脚本的入口 */ function main() { console.log('贝壳房源信息收集脚本 (v2.1) 已启动!'); createUI(); routePage(); } /** * 页面路由,根据当前URL决定执行哪个函数 */ function routePage() { const url = window.location.href; if (url.includes('/chengjiao/')) { console.log('进入成交列表页模式'); handleChengjiaoListPage(); } else if (url.includes('/ershoufang/') && url.endsWith('.html')) { console.log('进入在售详情页模式'); // 在售详情页,通过点击按钮来保存 } } // ================================================================== // 在售(ershoufang)页面处理逻辑 // ================================================================== /** * 点击“一键保存”按钮时触发的函数 */ function saveErshoufangDetail() { console.log('开始抓取在售房源详情...'); // 辅助函数,用于安全地获取元素文本 const getText = (selector) => document.querySelector(selector)?.innerText.trim() || ''; // 辅助函数,用于从“基本属性”和“交易属性”列表中提取信息 const getFromInfoList = (label) => { const allLi = document.querySelectorAll('.base .content li, .transaction .content li'); for (const li of allLi) { if (li.querySelector('.label')?.innerText === label) { const clone = li.cloneNode(true); clone.querySelector('.label').remove(); return clone.innerText.trim(); } } return ''; }; // 1. 抓取原始数据 const rawData = { title: getText('h1.main'), totalPrice: getText('.price .total'), unitPrice: getText('.unitPriceValue'), community: getText('.communityName > a.info'), fullArea: getText('.areaName > .info'), tags: Array.from(document.querySelectorAll('.tags .content .tag')).map(el => el.innerText.trim()).join(' | '), followerCount: getText('#favCount'), // 从基本属性列表中提取 layout: getFromInfoList('房屋户型'), floor: getFromInfoList('所在楼层'), grossArea: getFromInfoList('建筑面积'), structure: getFromInfoList('户型结构'), buildingType: getFromInfoList('建筑类型'), direction: getFromInfoList('房屋朝向'), decoration: getFromInfoList('装修情况'), elevatorRatio: getFromInfoList('梯户比例'), // 从交易属性列表中提取 listDate: getFromInfoList('挂牌时间'), ownership: getFromInfoList('交易权属'), lastTrade: getFromInfoList('上次交易'), usage: getFromInfoList('房屋用途'), propertyAge: getFromInfoList('房屋年限'), propertyRight: getFromInfoList('产权所属'), mortgage: getFromInfoList('抵押信息'), // 从另一个位置获取更准确的年代和建筑类型 yearAndBuildTypeFromSubInfo: getText('.houseInfo .area .subInfo'), }; // 2. 解析和格式化数据 const formatted = {}; formatted['标题'] = rawData.title; formatted['小区'] = rawData.community; const areaParts = rawData.fullArea.split(/\s+/).filter(Boolean); formatted['区域'] = areaParts[0] || 'N/A'; formatted['商圈'] = areaParts[1] || 'N/A'; formatted['总价(万)'] = parseFloat(rawData.totalPrice) || 'N/A'; formatted['单价(元/平)'] = parseInt(rawData.unitPrice) || 'N/A'; formatted['户型'] = rawData.layout; formatted['建筑面积(㎡)'] = parseFloat(rawData.grossArea) || 'N/A'; formatted['朝向'] = rawData.direction; formatted['装修'] = rawData.decoration; formatted['楼层'] = rawData.floor ? rawData.floor.split('咨询楼层')[0].trim() : 'N/A'; if (rawData.yearAndBuildTypeFromSubInfo) { const yearMatch = rawData.yearAndBuildTypeFromSubInfo.match(/(\d{4})年建/); formatted['年代'] = yearMatch ? parseInt(yearMatch[1]) : 'N/A'; const buildTypeMatch = rawData.yearAndBuildTypeFromSubInfo.match(/建\/(.+)/); formatted['建筑类型'] = buildTypeMatch ? buildTypeMatch[1].trim() : 'N/A'; } else { formatted['年代'] = 'N/A'; formatted['建筑类型'] = rawData.buildingType; } formatted['户型结构'] = rawData.structure; formatted['梯户比例'] = rawData.elevatorRatio; formatted['挂牌时间'] = rawData.listDate; formatted['交易权属'] = rawData.ownership; formatted['上次交易'] = rawData.lastTrade; formatted['房屋用途'] = rawData.usage; formatted['房屋年限'] = rawData.propertyAge; formatted['产权所属'] = rawData.propertyRight; formatted['抵押信息'] = rawData.mortgage.replace(/\s*查看详情\s*/g, '').trim(); formatted['房源标签'] = rawData.tags; formatted['关注人数'] = parseInt(rawData.followerCount) || 0; formatted['详情链接'] = window.location.href; // 3. 保存数据 let allData = JSON.parse(GM_getValue(STORAGE_KEYS.ERSHOUFANG) || '{}'); allData[window.location.href] = formatted; GM_setValue(STORAGE_KEYS.ERSHOUFANG, JSON.stringify(allData)); // 4. 更新UI反馈 const count = Object.keys(allData).length; updateButtonCount('ershoufang', count); const saveBtn = document.getElementById('gemini-save-ershoufang-btn'); saveBtn.innerText = '已保存!'; saveBtn.style.backgroundColor = '#67c23a'; // 绿色表示成功 setTimeout(() => { saveBtn.innerText = '一键保存本页信息'; saveBtn.style.backgroundColor = '#409EFF'; }, 1500); console.log('在售房源保存成功:', formatted); } // ================================================================== // 成交(chengjiao)页面处理逻辑 (无变动) // ================================================================== function handleChengjiaoListPage() { let allCollectedData = JSON.parse(GM_getValue(STORAGE_KEYS.CHENGJIAO) || '{}'); const items = document.querySelectorAll('ul.listContent > li'); if (items.length === 0) return; console.log(`在成交列表找到 ${items.length} 个房源,开始处理...`); items.forEach(item => { const titleElement = item.querySelector('div.info > div.title > a'); if (!titleElement) return; const getText = (selector) => item.querySelector(selector)?.innerText.trim() || ''; const rawHouseData = { title: getText('div.info > div.title > a'), detailUrl: titleElement.href, houseInfo: getText('div.houseInfo'), positionInfo: getText('div.positionInfo'), dealDate: getText('div.dealDate'), totalPrice: getText('div.totalPrice span.number'), unitPrice: getText('div.unitPrice span.number'), dealCycleInfo: getText('div.dealCycleeInfo .dealCycleTxt') }; const formattedData = parseChengjiaoData(rawHouseData); allCollectedData[formattedData.详情链接] = formattedData; }); GM_setValue(STORAGE_KEYS.CHENGJIAO, JSON.stringify(allCollectedData)); const finalCount = Object.keys(allCollectedData).length; console.log(`处理完毕!目前总共收集了 ${finalCount} 条成交房源信息。`); updateButtonCount('chengjiao', finalCount); } function parseChengjiaoData(rawData) { const formatted = { '小区名称': 'N/A', '户型': 'N/A', '面积(㎡)': 'N/A', '详情链接': rawData.detailUrl, '成交日期': rawData.dealDate, '成交总价(万)': rawData.totalPrice, '成交单价(元/平)': rawData.unitPrice, '朝向': 'N/A', '装修': 'N/A', '楼层信息': 'N/A', '建成年代': 'N/A', '房屋结构': 'N/A', '挂牌价(万)': 'N/A', '成交周期(天)': 'N/A' }; if (rawData.title) { const titleParts = rawData.title.split(/\s+/).filter(Boolean); if (titleParts.length >= 3) { formatted['面积(㎡)'] = parseFloat(titleParts[titleParts.length - 1]) || 'N/A'; formatted['户型'] = titleParts[titleParts.length - 2]; formatted['小区名称'] = titleParts.slice(0, -2).join(' '); } else { formatted['小区名称'] = rawData.title; } } if (rawData.houseInfo && rawData.houseInfo.includes('|')) { const parts = rawData.houseInfo.split('|'); formatted['朝向'] = parts[0] ? parts[0].trim() : 'N/A'; formatted['装修'] = parts[1] ? parts[1].trim() : 'N/A'; } else { formatted['朝向'] = rawData.houseInfo; } if (rawData.positionInfo) { const parts = rawData.positionInfo.split(/\s+/).filter(Boolean); formatted['楼层信息'] = parts[0] || 'N/A'; const yearAndStructurePart = parts.find(p => p.includes('年')); if (yearAndStructurePart) { const yearMatch = yearAndStructurePart.match(/(\d{4})年/); if (yearMatch) formatted['建成年代'] = parseInt(yearMatch[1]); const structureMatch = yearAndStructurePart.match(/年(.+)/); if (structureMatch) formatted['房屋结构'] = structureMatch[1].trim(); } } if (rawData.dealCycleInfo) { let match; match = rawData.dealCycleInfo.match(/挂牌(\d+\.?\d*)万/); if (match) formatted['挂牌价(万)'] = parseFloat(match[1]); match = rawData.dealCycleInfo.match(/成交周期(\d+)天/); if (match) formatted['成交周期(天)'] = parseInt(match[1]); } return formatted; } // ================================================================== // UI 和通用功能函数 // ================================================================== /** * 创建界面元素 */ function createUI() { const url = window.location.href; const container = document.createElement('div'); let buttonsHtml = ''; // 根据页面类型显示不同的按钮组合 if (url.includes('/ershoufang/')) { buttonsHtml = `