活动介绍

load-more-button是在循环遍历某个ul时候出现

时间: 2025-08-24 13:22:09 浏览: 5
<think>好的,我现在需要解决用户的问题,将DrissionPage集成到Scrapy中处理点击“加载更多”按钮,并结合response.css进行数据提取。用户之前已经得到了一些指导,现在想更深入了解具体的实现方法。首先,我需要回顾用户提供的引用内容,看看是否有相关的信息可以参考。 用户提供的引用[1]提到items.py的作用,定义数据结构,这可能对数据存储有帮助,但当前问题更多集中在中间件和页面交互上。引用[2]和[3]涉及Playwright和中间件的使用,尤其是处理动态网站和分页。引用[4]是关于Scrapy中解析URL和处理请求的示例,可能与如何触发后续请求有关。 用户的上一次回答中,已经提到了创建自定义中间件,使用DrissionPage处理动态加载,并将结果转换为HtmlResponse。现在用户可能希望更详细的步骤,或者遇到问题需要进一步解决。 首先,我需要确认用户的需求:在Scrapy项目中,如何集成DrissionPage来处理需要点击加载更多按钮的情况,并且能够使用response.css解析数据。可能的挑战包括中间件的正确设置、处理动态加载内容与Scrapy请求的协调,以及数据提取的兼容性。 接下来,我需要结合之前的回答和引用内容,构建一个清晰的步骤指南。可能需要详细说明中间件的工作流程,如何触发点击事件,以及如何确保动态加载后的内容能够被response.css正确解析。此外,可能需要考虑错误处理、等待策略以及如何与Scrapy的异步机制协同工作。 用户可能还关心如何优化性能,比如减少不必要的点击次数,或者处理页面元素加载失败的情况。此外,如何将动态加载后的数据传递到Scrapy的items中,可能需要结合items.py的定义,确保数据结构正确。 另外,需要注意DrissionPage和Scrapy的结合可能带来的问题,比如请求拦截和响应的转换是否正确,以及如何处理可能出现的JavaScript执行问题。例如,在中间件中,使用DrissionPage获取页面内容后,是否能够正确生成HtmlResponse对象,使得后续的response.css能够正常工作。 还需要考虑代码示例的准确性,比如中间件中的process_request方法是否正确处理了请求,是否在点击加载更多按钮后等待足够的时间以确保内容加载完成。同时,在parse方法中如何发起带有meta标记的请求,以触发中间件的处理。 最后,可能需要验证整个过程是否可行,比如测试中间件是否能正确拦截请求,执行点击操作,并将完整页面内容返回给Scrapy的解析函数。如果有错误,可能需要调试中间件中的代码,查看是否有元素定位失败或页面加载不完全的情况。 综上所述,我需要提供一个结构化的回答,涵盖中间件的创建、配置、动态加载处理、数据提取以及可能的问题和解决方案。同时,确保示例代码正确,并且与用户之前的问题和引用内容中的信息一致,避免出现矛盾或错误。</think>将DrissionPage集成到Scrapy中处理动态加载并保持response.css解析能力,可通过以下技术方案实现: ### 一、完整集成流程(含代码更新版) #### 1. 创建混合型中间件 ```python # middlewares.py from scrapy.http import HtmlResponse from drissionpage import SessionPage class HybridMiddleware: def process_request(self, request, spider): if request.meta.get('use_drissionpage'): page = SessionPage() page.get(request.url) # 动态加载处理逻辑 while True: load_btn = page.ele('#load-more-btn', timeout=3) if not load_btn: break load_btn.click() page.wait.load_start() # 使用内置等待新加载开始 page.wait.load_complete() # 等待加载完成[^1] return HtmlResponse( url=page.url, body=page.html.encode('utf-8'), encoding='utf-8', request=request ) ``` #### 2. 爬虫调用示例 ```python class ProductSpider(scrapy.Spider): name = 'dynamic_spider' def start_requests(self): yield scrapy.Request( url='https://example.com/products', callback=self.parse_initial, meta={'use_drissionpage': False} # 初始请求不使用DrissionPage ) def parse_initial(self, response): # 常规CSS选择器提取数据 yield {'page_title': response.css('title::text').get()} # 触发动态加载请求 yield response.follow( response.url, callback=self.parse_full_data, meta={'use_drissionpage': True} # 启用DrissionPage处理 ) def parse_full_data(self, response): # 处理完整加载后的数据 for product in response.css('div.product-item'): item = { 'name': product.css('h2::text').get().strip(), 'price': product.css('.price::text').re_first(r'\d+\.\d+'), 'specs': product.xpath('.//ul[@class="spec-list"]/li/text()').getall() } yield item ``` ### 二、关键技术实现原理 1. **混合解析机制**: - 初始请求使用原生Scrapy下载器($response.meta['use_drissionpage']=False$) - 动态加载请求通过中间件切换为DrissionPage渲染($response.meta['use_drissionpage']=True$) 2. **动态加载检测**: ```python page.wait.load_start() # 监测到新网络请求时触发 page.wait.load_complete(timeout=10) # 等待所有资源加载完成[^2] ``` 3. **选择器兼容性保障**: - DrissionPage生成的HTML通过$HtmlResponse$封装后,保持与原生response.css相同的DOM结构 - 支持混合使用CSS选择器和XPath: ```python response.css('div.price::text').get() response.xpath('//span[@class="discount"]/text()').get() ``` ### 三、性能优化策略 1. **智能等待配置**: ```python page.set.load_strategy.normal() # 平衡模式 page.set.timeouts( base=10, # 基础等待时间 page_load=30 # 页面加载超时 ) ``` 2. **元素定位优化**: ```python # 使用模糊匹配提高按钮识别率 page.ele('text:加载更多', timeout=5).click() ``` 3. **请求过滤机制**: ```python page.set.blocked_urls(['*.png', '*.gif']) # 屏蔽非必要资源 ``` ### 四、异常处理方案 ```python try: while page.ele('tag:button@text():Load More', timeout=2): page.ele('tag:button@text():Load More').click() page.wait(2) except PageElementNotFoundError: self.logger.info('No more items to load') except PageTimeoutError as e: self.logger.warning(f'Loading timeout: {str(e)}') ``` ### 五、与Item Pipeline的集成 ```python # items.py import scrapy class ProductItem(scrapy.Item): name = scrapy.Field() price = scrapy.Field( input_processor=MapCompose(str.strip), output_processor=TakeFirst() ) specs = scrapy.Field( output_processor=Join(',') ) ```
阅读全文

相关推荐

<input autocomplete="off" spellcheck="false" type="text" placeholder="考核目标" class="ivu-input ivu-input-default"> <input autocomplete="off" spellcheck="false" type="text" placeholder="考核标准" class="ivu-input ivu-input-default"> <button data-v-3c1c8183="" type="button" class="btns ivu-btn ivu-btn-info" style="width: 56px;"> 查询 </button><button data-v-3c1c8183="" type="button" class="btns ivu-btn ivu-btn-primary"> 导入 </button><button data-v-3c1c8183="" type="button" class="btns ivu-btn ivu-btn-primary"> 导出 </button><button data-v-3c1c8183="" type="button" class="btns ivu-btn ivu-btn-warning"> 新增 </button><button data-v-3c1c8183="" type="button" class="btns ivu-btn ivu-btn-error"> 删除 </button> <label class="ivu-checkbox-wrapper ivu-checkbox-default"> <input type="checkbox" class="ivu-checkbox-input"> </label> 分类 序号 权重(%) 考核目标 考核标准 操作 <label class="ivu-checkbox-wrapper ivu-checkbox-default"> <input type="checkbox" class="ivu-checkbox-input"> </label> 关键业务指标 1 20 考核目标需要SMART化;例如:在XX时间按XX标准完成XX项目交付。 考核目标需要SMART化;例如:在XX时间按XX标准完成XX项目交付。 ①在2024年X月X日前,完成包括考核目标内的其他模块开发交付,且无bug,为优秀;②在2024年X月X日前,完成考核目标要求的模块开发交付,且无bug,为合格;③2024年X月X日未完成考核目标要求的模块开发交付,或完成后出现bug,为不足 <button type="button" class="ivu-btn ivu-btn-success ivu-btn-small" style="display: inline-block;"> 编辑</button> <label class="ivu-checkbox-wrapper ivu-checkbox-default"> <input type="checkbox" class="ivu-checkbox-input"> </label> 关键业务指标 2 20 考核目标需要SMART化;例如:在XX时间按XX标准完成XX项目交付。 考核目标需要SMART化;例如:在XX时间按XX标准完成XX项目交付。 ①在2024年X月X日前,完成包括考核目标内的其他模块开发交付,且无bug,为优秀;②在2024年X月X日前,完成考核目标要求的模块开发交付,且无bug,为合格;③2024年X月X日未完成考核目标要求的模块开发交付,或完成后出现bug,为不足 <button type="button" class="ivu-btn ivu-btn-success ivu-btn-small" style="display: inline-block;"> 编辑</button> <label class="ivu-checkbox-wrapper ivu-checkbox-default"> <input type="checkbox" class="ivu-checkbox-input"> </label> 关键业务指标 3 20 考核目标需要SMART化;例如:在XX时间按XX标准完成XX项目交付。 考核目标需要SMART化;例如:在XX时间按XX标准完成XX项目交付。 ①在2024年X月X日前,完成包括考核目标内的其他模块开发交付,且无bug,为优秀;②在2024年X月X日前,完成考核目标要求的模块开发交付,且无bug,为合格;③2024年X月X日未完成考核目标要求的模块开发交付,或完成后出现bug,为不足 <button type="button" class="ivu-btn ivu-btn-success ivu-btn-small" style="display: inline-block;"> 编辑</button> <label class="ivu-checkbox-wrapper ivu-checkbox-default"> <input type="checkbox" class="ivu-checkbox-input"> </label> 关键业务指标 4 20 考核目标需要SMART化;例如:在XX时间按XX标准完成XX项目交付。 考核目标需要SMART化;例如:在XX时间按XX标准完成XX项目交付。 ①在2024年X月X日前,完成包括考核目标内的其他模块开发交付,且无bug,为优秀;②在2024年X月X日前,完成考核目标要求的模块开发交付,且无bug,为合格;③2024年X月X日未完成考核目标要求的模块开发交付,或完成后出现bug,为不足 <button type="button" class="ivu-btn ivu-btn-success ivu-btn-small" style="display: inline-block;"> 编辑</button> <label class="ivu-checkbox-wrapper ivu-checkbox-default"> <input type="checkbox" class="ivu-checkbox-input"> </label> 价值观指标 5 10 试用期内进入XX项目,并通过客户考核 试用期内进入XX项目,并通过客户考核 ①通过则合格②未通过且未进入其他项目的为不合格,原则上不能转正。 ①通过则合格②未通过且未进入其他项目的为不合格,原则上不能转正。 <button type="button" class="ivu-btn ivu-btn-success ivu-btn-small" style="display: inline-block;"> 编辑</button> <label class="ivu-checkbox-wrapper ivu-checkbox-default"> <input type="checkbox" class="ivu-checkbox-input"> </label> 价值观指标 6 10 综合素质 ①积极主动,责任心强,严格自律,能自觉遵守公司各项规章制度; ②接受任务主动,责任心较强,能较准确接受上级指示,遵守公司的各项规章制度; ③接受任务不主动,责任心不强,有违纪现象。 ④能主动积极与本部门其他成员建立良好的协作关系,保持良好的团队合作气氛;与部门外、公司外人员,建立良好的人际关系,促进业务上的沟通; ⑤能与其他成员建立较好的协作关系,保持较好的团队合作气氛;与部门外、公司外人员,建立较好的人际关系,促进业务沟通; ⑥不能很好保持的团队合作气氛;与部门内、外、公司外人员,人际关系较弱,不能很好促进工作沟通。 ⑦为人礼貌、正直、豁达,能实事求是阐述问题,有良好的适应能力,能很快加入公司的团队,能主动更新业务知识; ⑧为人诚实,较能实事求是阐述问题,有较好的适应能力,融入公司的团队较快,更新业务知识较强; ⑨有一定性格问题,阐述问题有片面性,适应能力较弱,融入公司的团队较难,不能主动更新业务知识。 <button type="button" class="ivu-btn ivu-btn-success ivu-btn-small" style="display: inline-block;"> 编辑</button> 暂无筛选结果 操作 <label class="ivu-checkbox-wrapper ivu-checkbox-default"> <input type="checkbox" class="ivu-checkbox-input"> </label> 分类 序号 权重(%) 考核目标 考核标准 <button type="button" class="ivu-btn ivu-btn-success ivu-btn-small" style="display: inline-block;"> 编辑</button> <label class="ivu-checkbox-wrapper ivu-checkbox-default"> <input type="checkbox" class="ivu-checkbox-input"> </label> 关键业务指标 1 20 考核目标需要SMART化;例如:在XX时间按XX标准完成XX项目交付。 考核目标需要SMART化;例如:在XX时间按XX标准完成XX项目交付。 ①在2024年X月X日前,完成包括考核目标内的其他模块开发交付,且无bug,为优秀;②在2024年X月X日前,完成考核目标要求的模块开发交付,且无bug,为合格;③2024年X月X日未完成考核目标要求的模块开发交付,或完成后出现bug,为不足 <button type="button" class="ivu-btn ivu-btn-success ivu-btn-small" style="display: inline-block;"> 编辑</button> <label class="ivu-checkbox-wrapper ivu-checkbox-default"> <input type="checkbox" class="ivu-checkbox-input"> </label> 关键业务指标 2 20 考核目标需要SMART化;例如:在XX时间按XX标准完成XX项目交付。 考核目标需要SMART化;例如:在XX时间按XX标准完成XX项目交付。 ①在2024年X月X日前,完成包括考核目标内的其他模块开发交付,且无bug,为优秀;②在2024年X月X日前,完成考核目标要求的模块开发交付,且无bug,为合格;③2024年X月X日未完成考核目标要求的模块开发交付,或完成后出现bug,为不足 <button type="button" class="ivu-btn ivu-btn-success ivu-btn-small" style="display: inline-block;"> 编辑</button> <label class="ivu-checkbox-wrapper ivu-checkbox-default"> <input type="checkbox" class="ivu-checkbox-input"> </label> 关键业务指标 3 20 考核目标需要SMART化;例如:在XX时间按XX标准完成XX项目交付。 考核目标需要SMART化;例如:在XX时间按XX标准完成XX项目交付。 ①在2024年X月X日前,完成包括考核目标内的其他模块开发交付,且无bug,为优秀;②在2024年X月X日前,完成考核目标要求的模块开发交付,且无bug,为合格;③2024年X月X日未完成考核目标要求的模块开发交付,或完成后出现bug,为不足 <button type="button" class="ivu-btn ivu-btn-success ivu-btn-small" style="display: inline-block;"> 编辑</button> <label class="ivu-checkbox-wrapper ivu-checkbox-default"> <input type="checkbox" class="ivu-checkbox-input"> </label> 关键业务指标 4 20 考核目标需要SMART化;例如:在XX时间按XX标准完成XX项目交付。 考核目标需要SMART化;例如:在XX时间按XX标准完成XX项目交付。 ①在2024年X月X日前,完成包括考核目标内的其他模块开发交付,且无bug,为优秀;②在2024年X月X日前,完成考核目标要求的模块开发交付,且无bug,为合格;③2024年X月X日未完成考核目标要求的模块开发交付,或完成后出现bug,为不足 <button type="button" class="ivu-btn ivu-btn-success ivu-btn-small" style="display: inline-block;"> 编辑</button> <label class="ivu-checkbox-wrapper ivu-checkbox-default"> <input type="checkbox" class="ivu-checkbox-input"> </label> 价值观指标 5 10 试用期内进入XX项目,并通过客户考核 试用期内进入XX项目,并通过客户考核 ①通过则合格②未通过且未进入其他项目的为不合格,原则上不能转正。 ①通过则合格②未通过且未进入其他项目的为不合格,原则上不能转正。 <button type="button" class="ivu-btn ivu-btn-success ivu-btn-small" style="display: inline-block;"> 编辑</button> <label class="ivu-checkbox-wrapper ivu-checkbox-default"> <input type="checkbox" class="ivu-checkbox-input"> </label> 价值观指标 6 10 综合素质 ①积极主动,责任心强,严格自律,能自觉遵守公司各项规章制度; ②接受任务主动,责任心较强,能较准确接受上级指示,遵守公司的各项规章制度; ③接受任务不主动,责任心不强,有违纪现象。 ④能主动积极与本部门其他成员建立良好的协作关系,保持良好的团队合作气氛;与部门外、公司外人员,建立良好的人际关系,促进业务上的沟通; ⑤能与其他成员建立较好的协作关系,保持较好的团队合作气氛;与部门外、公司外人员,建立较好的人际关系,促进业务沟通; ⑥不能很好保持的团队合作气氛;与部门内、外、公司外人员,人际关系较弱,不能很好促进工作沟通。 ⑦为人礼貌、正直、豁达,能实事求是阐述问题,有良好的适应能力,能很快加入公司的团队,能主动更新业务知识; ⑧为人诚实,较能实事求是阐述问题,有较好的适应能力,融入公司的团队较快,更新业务知识较强; ⑨有一定性格问题,阐述问题有片面性,适应能力较弱,融入公司的团队较难,不能主动更新业务知识。 <object tabindex="-1" type="text/html" data="about:blank" style="display: block; position: absolute; top: 0px; left: 0px; width: 100%; height: 100%; border: none; padding: 0px; margin: 0px; opacity: 0; z-index: -1000; pointer-events: none;"></object> 1 <input type="hidden" value="10"> 10 条/页 无匹配数据 10 条/页20 条/页50 条/页100 条/页 加载中 跳至 <input type="text" autocomplete="off" spellcheck="false"> 页 第 1 - 6 共 6 <button data-v-3c1c8183="" type="button" class="refresh ivu-btn ivu-btn-default ivu-btn-circle ivu-btn-small ivu-btn-icon-only"> </button> 1 document.querySelector("div.cover .dataContent .ivu-col .ivu-card-body "); 打印如上面的html 结构 2 完善查询 找到导入按钮

function https(url,data,fu){ $.ajax({ contentType:“application/json”, url: url, // 假设后端 API 地址 method: “POST”, data: JSON.stringify(data), type: “json”, dataType: “json”, success:function(e){ console.log(e.data) if(e.status==200){ console.log(e.data) fu(e.data) }else{ alert(e.text) } }, error: function (e) { console.log(e.data) console.error(e) alert(“请求失败,请稍后再试!”+e); } }); } function checkLoginStatus() { if (window.location.href.includes(‘/login.html’)) return; } window.addEventListener(‘beforeunload’, function(e) { console.trace(‘触发页面跳转的堆栈跟踪:’); }); //安全验证函数 function validateUserSession() { try { // 防御性数据获取 + 数据清洗 const rawValue = localStorage.getItem(“name”); const username = String(rawValue ?? ‘’) .trim() .replace(/[\u200B-\u200D\uFEFF]/g, ‘’); // 移除零宽字符 // 调试信息 console.log('[Auth] Raw:', rawValue, 'Processed:', username); // 复合验证条件 const isValid = ( username.length >= 2 && // 最小长度要求 !/[<>]/.test(username) && // 防止XSS username !== 'null' && username !== 'undefined' ); if (!isValid) { console.warn('无效用户标识:', username); // 安全跳转方法 //window.location.replace('/KuCun2/login.html'); // 立即终止执行 return Promise.reject('Invalid session'); } // 返回清洗后的用户名 return username; } catch (error) { console.error(‘会话验证失败:’, error); // window.location.replace(‘/KuCun2/login.html’); return Promise.reject(error); } } function deepMergeArrays(frontend, backend) { const resultMap = new Map(); // 遍历前端数据并存入 Map 中以便快速查找 frontend.forEach(item => resultMap.set(item.id, { ...item })); // 遍历后端数据并与前端数据进行合并 backend.forEach(item => { if (resultMap.has(item.id)) { // 如果存在相同 ID,则合并两者的内容 resultMap.set( item.id, Object.assign(resultMap.get(item.id), item) ); } else { // 如果不存在相同 ID,则新增该条目 resultMap.set(item.id, { ...item }); } }); // 将最终结果转回数组形式 return Array.from(resultMap.values()); } (function ($){ // 页面加载时检查登录状态 checkLoginStatus(); })(jQuery); function removeSpecificCharactersAndConvertToNumber(str, charsToRemove) { const regex = new RegExp([${charsToRemove}], ‘g’); // 创建用于匹配指定字符的正则表达式 const cleanedStr = str.replace(regex, ‘’); // 移除指定字符 const numberValue = parseFloat(cleanedStr); // 转换为浮点数 return isNaN(numberValue) ? null : numberValue; // 如果无法解析,则返回 null } <modelVersion>4.0.0</modelVersion> <groupId>KuCun2</groupId> <artifactId>KuCun2</artifactId> <version>0.0.1-SNAPSHOT</version> war <name>KuCun2</name> <description/> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.3.12.RELEASE</version> <relativePath/> <webVersion>4.0</webVersion> UTF-8 3.21.12 <dependencies> <dependency> <groupId>javax</groupId> <artifactId>javaee-api</artifactId> <version>8.0</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.glassfish.web</groupId> <artifactId>javax.servlet.jsp.jstl</artifactId> <version>1.2.4</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.33</version> <exclusions> <exclusion> <groupId>com.google.protobuf</groupId> <artifactId>protobuf-java</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> <scope>provided</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <exclusions> <exclusion> <groupId>com.fasterxml.jackson.datatype</groupId> <artifactId>jackson-datatype-jsr310</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>com.fasterxml.jackson.datatype</groupId> <artifactId>jackson-datatype-jsr310</artifactId> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-core</artifactId> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-annotations</artifactId> </dependency> <dependency> <groupId>org.mindrot</groupId> <artifactId>jbcrypt</artifactId> <version>0.4</version> </dependency> <dependency> <groupId>com.google.protobuf</groupId> <artifactId>protobuf-java</artifactId> <version>${protobuf.version}</version> </dependency> <dependency> <groupId>org.yaml</groupId> <artifactId>snakeyaml</artifactId> <version>1.30</version> </dependency> </dependencies> <build> <artifactId>maven-compiler-plugin</artifactId> <version>2.3.2</version> <configuration> <source>1.8</source> <target>1.8</target> <compilerArgs> <arg>-parameters</arg> </compilerArgs> </configuration> <artifactId>maven-war-plugin</artifactId> <version>2.6</version> <configuration> <failOnMissingWebXml>false</failOnMissingWebXml> </configuration> </build> package com.kucun; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; @SpringBootApplication( ) public class DemoApplication extends SpringBootServletInitializer { public static void main(String[] args) { // // ConfigurableApplicationContext ctx = SpringApplication.run(Application.class, args); // Arrays.stream(ctx.getBeanNamesForType(SecurityFilterChain.class)) // .forEach(System.out::println); // // // // 测试密码加密示例 // BCryptPasswordEncoder encoder = new BCrypt(); // String rawPassword = “987987”; // String encodedPassword = encoder.encode(rawPassword); // System.out.println(“加密后的密码:” + encodedPassword); SpringApplication.run(DemoApplication.class, args); } }package com.kucun.Config; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.kucun.Config.Role.RoleConverter; import com.kucun.Config.user.CustomUserDetails; import com.kucun.data.entity.User; import com.kucun.dataDo.UserRepository; /** 获取数据 @author Administrator / @Service public class CustomUserDetailsService/ implements UserDetailsService /{ // // @Autowired // private UserRepository userRepository; // // @Autowired // private RoleConverter roleConverter; // // public CustomUserDetailsService() { // // super(); // System.out.println(“11111”); // } ///* // * 获取数据库中用户信息 // * @param andy 账号 // * // * @return // * // */ // @Override // public UserDetails loadUserByUsername(String andy) { // System.out.println(andy); // User user = userRepository.findByAndy(andy); // System.out.println(user); // return new CustomUserDetails(user, // roleConverter.convert(user.getRole()) // 关键转换点1 // ); // } }package com.kucun.Config; // 2. 基础安全配置 //@Configuration //@EnableWebSecurity // 启用Web安全功能 public class SecurityConfig //extends WebSecurityConfigurerAdapter { // @Override // public void configure(WebSecurity web) { // web.ignoring().antMatchers(“/check-session”); // } // // 添加自定义Controller // @RestController // public static class SessionCheckController { // @GetMapping(“/check-session”) // public ResponseEntity<?> checkSession(HttpServletRequest request) { // return request.getSession(false) != null ? // ResponseEntity.ok().build() : // ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); // } // } // /** // * 核心安全过滤器链配置 // * @param http HTTP安全构建器 // * @return 安全过滤器链 // * @throws Exception 配置异常 // * // * █ 配置逻辑说明: // * 1. authorizeHttpRequests: 定义访问控制规则 // * 2. formLogin: 配置表单登录 // * 3. logout: 配置注销行为 // * 4. exceptionHandling: 处理权限异常3 // / // // // 修正后的配置方法 // @Override // protected void configure(HttpSecurity http) throws Exception { // // // // http // .csrf().disable() // .sessionManagement() // .sessionCreationPolicy(SessionCreationPolicy.ALWAYS) // .invalidSessionUrl(“/login.html?session=invalid”) // .maximumSessions(1) // .maxSessionsPreventsLogin(false) // .and() // .and() // .addFilterBefore(jsonAuthFilter(), UsernamePasswordAuthenticationFilter.class) // 关键配置 // .authorizeRequests() // .antMatchers(“/login.html”, “/users/login”).permitAll() // .antMatchers(“/js/", "/css/”, “/fonts/", "/images/”,“/check-session”,“/main/bootstrap-3.3.7-dist/“).permitAll() // .antMatchers(”/users/guanli/”).hasAuthority(“ROLE_ADMIN”) // .anyRequest().authenticated() // .and() // .formLogin().disable() //// .loginPage(“/login.html”) //// .loginProcessingUrl(“/users/login”) //// //// .successHandler(ajaxAuthenticationSuccessHandler()) // 自定义成功处理器 //// .failureHandler(ajaxAuthenticationFailureHandler()) // 自定义失败处理器 //// .defaultSuccessUrl(“/index.html”) //// .failureUrl(“/login.html?error=true”) //// .usernameParameter(“andy”) // 修改用户名参数名 //// .passwordParameter(“pass”) // 修改密码参数名 //// .and() // // .logout() // .logoutUrl(“/logout”) // .logoutSuccessUrl(“/login.html”) // .and() // .csrf() // .ignoringAntMatchers(“/users/login”) // .and() // .headers() // .frameOptions().sameOrigin() // .and() // .exceptionHandling() // .accessDeniedHandler(accessDeniedHandler()); // 统一使用Handler // } // // // // // // // 返回JSON格式的成功响应 // @Bean // public AuthenticationSuccessHandler ajaxAuthenticationSuccessHandler() { // return (request, response, authentication) -> { // // // // // 强制创建服务端会话 // request.getSession(true); // // // // // String contextPath = request.getContextPath(); // HttpSession session = request.getSession(true); // Cookie cookie = new Cookie(“JSESSIONID”, session.getId()); // cookie.setPath(contextPath.isEmpty() ? “/” : contextPath + “/”); // cookie.setMaxAge(1800); // 30分钟 // response.addCookie(cookie); // // // //构建安全响应数据 // Map<String, Object> responseData = new HashMap<>(); // responseData.put(“sessionId”, request.getSession().getId()); // responseData.put(“userInfo”,Collections.unmodifiableMap(new HashMap<String, Object>() {/* // * // / // private static final long serialVersionUID = 1L; // // { // put(“Name”, ((CustomUserDetails)authentication.getPrincipal()).getName()); // put(“role”, ((CustomUserDetails)authentication.getPrincipal()).getRole()); // }})); // // // // // 统一返回JSON格式 // response.setContentType(MediaType.APPLICATION_JSON_VALUE); // // new ObjectMapper().writeValue(response.getWriter(), responseData); // // // // // // // // // // response.setContentType(MediaType.APPLICATION_JSON_VALUE); // CustomUserDetails userDetails = (CustomUserDetails) authentication.getPrincipal(); // // response.setStatus(HttpStatus.OK.value()); // System.out.println(authentication.getPrincipal()+“”+authentication.getName()); // // if (request.getHeader(“X-Requested-With”) == null) { // 非AJAX请求 // response.sendRedirect(“/index.html”); // } else { // // //String re=userDetails.getUser().toString() // new ObjectMapper().writeValue(response.getWriter(), userDetails.getUser() // ); // // } // // // // // // // }; // } // // // 返回401状态码和错误信息 // @Bean // public AuthenticationFailureHandler ajaxAuthenticationFailureHandler() { // return (request, response, exception) -> { // if (request.getHeader(“X-Requested-With”) == null) { // response.sendRedirect(“/login.html?error=true”); // } else { // response.setStatus(HttpStatus.UNAUTHORIZED.value()); // response.getWriter().write(“{"error":"Authentication failed"}”); // } // }; // } // // // 处理未认证请求 // @Bean // public AuthenticationEntryPoint ajaxAuthenticationEntryPoint() { // return (request, response, exception) -> { // if (request.getHeader(“X-Requested-With”) == null) { // response.sendRedirect(“/login.html?error=true”); // } else { // response.setStatus(HttpStatus.UNAUTHORIZED.value()); // response.getWriter().write(“{"error":"Authentication failed"}”); // } // }; // } // // // // // // @Bean // public JsonUsernamePasswordAuthenticationFilter jsonAuthFilter() throws Exception { // // JsonUsernamePasswordAuthenticationFilter filter = // new JsonUsernamePasswordAuthenticationFilter(); // filter.setAuthenticationManager(authenticationManagerBean()); // filter.setUsernameParameter(“andy”); // 设置自定义参数名 // filter.setPasswordParameter(“pass”); // filter.setFilterProcessesUrl(“/users/login”); // filter.setAuthenticationSuccessHandler(ajaxAuthenticationSuccessHandler()); // filter.setAuthenticationFailureHandler(ajaxAuthenticationFailureHandler()); // // return filter; // } // // // /* // * 密码编码器(必须配置) // * 使用BCrypt强哈希算法加密 // */ // @Bean // public PasswordEncoder passwordEncoder() { // return new BCryptPasswordEncoder(); // } // // // @Bean // public AccessDeniedHandler accessDeniedHandler() { // System.out.println(“0000”); // return (request, response, ex) -> { // if (!response.isCommitted()) { // response.sendRedirect(“/error/403”); // } // }; // } // // //} // // // // // // //class JsonUsernamePasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter { // private final ObjectMapper objectMapper = new ObjectMapper(); // // @Override // public Authentication attemptAuthentication(HttpServletRequest request, // HttpServletResponse response) // throws AuthenticationException { // System.out.println(“收到认证请求,路径:” + request.getRequestURI()); // System.out.println(“请求方法:” + request.getMethod()); // System.out.println(“Content-Type:” + request.getContentType()); // if (request.getContentType() != null && // request.getContentType().startsWith(MediaType.APPLICATION_JSON_VALUE)) { // // try (InputStream is = request.getInputStream()) { // Map<String, String> authMap = objectMapper.readValue(is, Map.class); // // String username = authMap.getOrDefault(getUsernameParameter(), “”); // String password = authMap.getOrDefault(getPasswordParameter(), “”); // // // 调试日志 // System.out.println("Authentication attempt with: " + username+‘_’+ password); // // UsernamePasswordAuthenticationToken authRequest = // new UsernamePasswordAuthenticationToken(username, password); // // setDetails(request, authRequest); // return this.getAuthenticationManager().authenticate(authRequest); // } catch (IOException e) { // throw new AuthenticationServiceException(“认证请求解析失败”, e); // } // } // Authentication aut= super.attemptAuthentication(request, response); // System.out.println(“结果:”+aut.isAuthenticated()); // // return aut; // } } package com.kucun.Config.Role; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.annotation.PostConstruct; import javax.json.Json; import org.springframework.stereotype.Component; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; /** 权限转化 @author Administrator */ @Component public class RoleConverter { // private static final Map<Integer, String> ROLE_MAP = new HashMap<>(); // // @PostConstruct // public void init() { // ROLE_MAP.put(0, “ROLE_ADMIN”); // ROLE_MAP.put(1, “ROLE_USER”); // ROLE_MAP.put(2, “ROLE_MANAGER”); // ROLE_MAP.put(3, “ROLE_AUDITOR”); // } // // public List<GrantedAuthority> convert(int roleCode) { // ObjectMapper mapper = new ObjectMapper(); // try { // System.out.println(mapper.writeValueAsString(Collections.singletonList( // new SimpleGrantedAuthority(ROLE_MAP.getOrDefault(roleCode, “ROLE_GUEST”)))).toString());//输出[{“authority”:“ROLE_ADMIN”}] // } catch (JsonProcessingException e) { // // TODO Auto-generated catch block // e.printStackTrace(); // } // // return Collections.singletonList( // new SimpleGrantedAuthority(ROLE_MAP.getOrDefault(roleCode, “ROLE_GUEST”)) // ); // } // }package com.kucun.Config.user; import java.util.Collection; import com.kucun.data.entity.User; public class CustomUserDetails /implements UserDetails/ { // /** // * // */ // private static final long serialVersionUID = 1L; // private final String andy; // 对应andy字段 // private final String name; // private final int role; // private final String password; // private final User users; // private final Collection<? extends GrantedAuthority> authorities; // // public CustomUserDetails(User user, Collection<? extends GrantedAuthority> authorities) { // this.andy = user.getAndy(); // this.name = user.getName(); // this.role = user.getRole(); // this.password = user.getPass(); // user.setPass(null); // this.users=user; // this.authorities = authorities; // } // // // 实现UserDetails接口方法 // @Override public String getUsername() { return andy; } // @Override public String getPassword() { return password; } // @Override public Collection<? extends GrantedAuthority> getAuthorities() { return authorities; } // // // 自定义字段访问方法 // public String getName() { return name; } // public User getUser() { return users; } // public int getRole() { return role; } // // // 其他必要方法 // @Override public boolean isAccountNonExpired() { return true; } // @Override public boolean isAccountNonLocked() { return true; } // @Override public boolean isCredentialsNonExpired() { return true; } // @Override public boolean isEnabled() { return true; } } <!doctype html> <html> <head> <meta charset=“utf-8”> <meta name=“viewport” content=“width=device-width, initial-scale=1, maximum-scale=1”> <title>峤丞板材库存管理</title> <meta name=“viewport” content=“width=device-width, initial-scale=1”> <style type=“text/css”> *{ margin:0; padding:0; } .frame-header { height: 60px; background-color: #23262E; justify-content: space-between; } .frame-header-li{ font-family: Arial, Helvetica, sans-serif; font-size:40px; } .frame-ul{ } .frame-ul li{ border-style: solid; border-width:1px 0px 1px 0px; margin-top: 1px; height: 35px; text-align: center } #username{ position: absolute; right: 0; /* 靠右 */ } .frame-body { position: fixed; top: 60px; right: 0; bottom: 0; left: 0; display: flex; flex-direction: row; } .frame-side { scrollbar-width: none; /* firefox隐藏滚动条 / -ms-overflow-style: none; / IE 10+隐藏滚动条 / overflow-x: hidden; overflow-y: auto; width: 200px; background-color:#9e5; } .frame-side::-webkit-scrollbar { display: none; / Chrome Safari 隐藏滚动条*/ } .frame-main { flex-grow: 1; background-color:#fff; } .jiaoluo{ margin: auto; margin-right: 0px; } </style> </head> <body> 峤丞木材仓库管理 峤丞木材仓库管理 <button class=“.menu-button” style=“”>注销</button> 首页 材料录入 材料录入 <iframe id="iframeid" name="main" src="main/index.html" width="100%" height="100%" frameborder="0"> </iframe> </body> <script type=“text/javascript” src=“js/jquery-3.2.1.min.js”></script> <script type=“text/javascript” src=“js/jsyilai.js”></script> </html><!DOCTYPE html> <html lang=“zh-CN”> <head> <title>扁平简洁的登录页面演示_dowebok</title> <meta charset=“utf-8”> <meta name=“viewport” content=“width=device-width, initial-scale=1”> </head> <body> 登 录 <form class="login100-form validate-form"> 用户名 <input class="input100" type="text" name="andy" placeholder="请输入用户名"> 密码 <input class="input100" type="password" name="pass" placeholder="请输入用户密码"> <input class="input-checkbox100" id="ckb1" type="checkbox" name="remember-me"> <label class="label-checkbox100" for="ckb1">记住我</label> 忘记密码? <button class="login100-form-btn">登 录</button> </form> <script type=“text/javascript”> </script> <script src=“js/jquery-3.2.1.min.js”></script> <script type=“text/javascript” src=“js/jsyilai.js”></script> </body> </html> yilai.js (function ($) { var pathName = window.location.pathname; console.log(pathName); // 输出类似于 ‘/index.html’ //alert(pathName) switch (pathName){ case "/KuCun2/login.html": jQuery.getScript('/KuCun2/js/main.js?'+Date.now()) jQuery.getScript('/KuCun2/js/login.js?'+Date.now()) break; case "/KuCun2/main/Guanli.html": jQuery.getScript('/KuCun2/js/main.js?'+Date.now()) jQuery.getScript('/KuCun2/js/guanli.js?'+Date.now()) jQuery.getScript('/KuCun2/main/bootstrap-3.3.7-dist/js/FileSaver.js?'+Date.now()) jQuery.getScript('/KuCun2/main/bootstrap-3.3.7-dist/js/MyTable.js?'+Date.now()) break; case "/KuCun2/index.html": jQuery.getScript('/KuCun2/js/main.js?'+Date.now()) jQuery.getScript('/KuCun2/js/index.js?'+Date.now()) break; } })(jQuery) main.js function https(url,data,fu){ $.ajax({ contentType:“application/json”, url: url, // 假设后端 API 地址 method: “POST”, data: JSON.stringify(data), type: “json”, dataType: “json”, success:function(e){ console.log(e.data) if(e.status==200){ console.log(e.data) fu(e.data) }else{ alert(e.text) } }, error: function (e) { console.log(e.data) console.error(e) alert(“请求失败,请稍后再试!”+e); } }); } function checkLoginStatus() { if (window.location.href.includes(‘/login.html’)) return; } window.addEventListener(‘beforeunload’, function(e) { console.trace(‘触发页面跳转的堆栈跟踪:’); }); //安全验证函数 function validateUserSession() { try { // 防御性数据获取 + 数据清洗 const rawValue = localStorage.getItem(“name”); const username = String(rawValue ?? ‘’) .trim() .replace(/[\u200B-\u200D\uFEFF]/g, ‘’); // 移除零宽字符 // 调试信息 console.log('[Auth] Raw:', rawValue, 'Processed:', username); // 复合验证条件 const isValid = ( username.length >= 2 && // 最小长度要求 !/[<>]/.test(username) && // 防止XSS username !== 'null' && username !== 'undefined' ); if (!isValid) { console.warn('无效用户标识:', username); // 安全跳转方法 //window.location.replace('/KuCun2/login.html'); // 立即终止执行 return Promise.reject('Invalid session'); } // 返回清洗后的用户名 return username; } catch (error) { console.error(‘会话验证失败:’, error); // window.location.replace(‘/KuCun2/login.html’); return Promise.reject(error); } } function deepMergeArrays(frontend, backend) { const resultMap = new Map(); // 遍历前端数据并存入 Map 中以便快速查找 frontend.forEach(item => resultMap.set(item.id, { ...item })); // 遍历后端数据并与前端数据进行合并 backend.forEach(item => { if (resultMap.has(item.id)) { // 如果存在相同 ID,则合并两者的内容 resultMap.set( item.id, Object.assign(resultMap.get(item.id), item) ); } else { // 如果不存在相同 ID,则新增该条目 resultMap.set(item.id, { ...item }); } }); // 将最终结果转回数组形式 return Array.from(resultMap.values()); } (function ($){ // 页面加载时检查登录状态 checkLoginStatus(); })(jQuery); function removeSpecificCharactersAndConvertToNumber(str, charsToRemove) { const regex = new RegExp([${charsToRemove}], ‘g’); // 创建用于匹配指定字符的正则表达式 const cleanedStr = str.replace(regex, ‘’); // 移除指定字符 const numberValue = parseFloat(cleanedStr); // 转换为浮点数 return isNaN(numberValue) ? null : numberValue; // 如果无法解析,则返回 null } logi.js/// <reference path=“jquery.d.ts” /> // ParseError: KaTeX parse error: Expected '}', got 'EOF' at end of input: …function(){ // (“#submit”).click(function(){ // // // ParseError: KaTeX parse error: Expected '}', got 'EOF' at end of input: … // andy:(“#andy”).val(), // pass:ParseError: KaTeX parse error: Expected 'EOF', got '#' at position 3: ("#̲pass").val(), /…(“#name”).val()}), // type:“post”, // contentType:“application/json”, // success:function(e){ // alert(e) // } // }); // // // }); // }) (function ($) { “use strict”; /*================================================================== [ Focus Contact2 ]*/ $('.input100').each(function () { $(this).on('blur', function () { if ($(this).val().trim() != "") { $(this).addClass('has-val'); } else { $(this).removeClass('has-val'); } }) }) /*================================================================== [ Validate ]*/ var input = $('.validate-input .input100'); $('.validate-form').on('submit', function (e) { e.preventDefault(); var check = true; for (var i = 0; i < input.length; i++) { if (validate(input[i]) == false) { showValidate(input[i]); check = false; } } confirm(input) if(check) login(input); return check; }); $('.validate-form .input100').each(function () { $(this).focus(function () { hideValidate(this); }); }); function validate(input) { if ($(input).attr('type') == 'email' || $(input).attr('name') == 'email') { if ($(input).val().trim().match(/^([a-zA-Z0-9_\-\.]+)@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([a-zA-Z0-9\-]+\.)+))([a-zA-Z]{1,5}|[0-9]{1,3})(\]?)$/) == null) { return false; } } else { if ($(input).val().trim() == '') { return false; } } } function showValidate(input) { var thisAlert = $(input).parent(); alert(input) $(thisAlert).addClass('alert-validate'); } function hideValidate(input) { var thisAlert = $(input).parent(); $(thisAlert).removeClass('alert-validate'); } // 登录按钮点击事件 function login(datas) { var data={} datas.each(function(a,element){ data[$(element).attr('name')]=$(element).val() }) alert(data.name); //var data={ andy,pass } // 模拟 AJAX 请求 https("/KuCun2/users/login",data,function (response) { alert("122222"); if (response.name) { localStorage.setItem("name", response.name); // 保存用户名到本地存储 localStorage.setItem("role", response.role); // 保存权限到本地存储 alert( response.name) window.location.href = '/KuCun2/index.html'; } else { alert("登录失败,请检查用户名和密码!"); } }) }; // 注销按钮点击事件 $("#logout-btn").click(function () { localStorage.removeItem("name"); // 清除本地存储中的用户名 checkLoginStatus(); // 更新登录状态 }); })(jQuery);index.js$().ready(function () { const username = localStorage.getItem("name"); $("#username").text(username) const $username = $('#username'); const $menuButton = $('.menu-button'); let hideTimeout = null; // 点击显示按钮 $username.on('click', function(e) { e.stopPropagation(); $menuButton.show(); }); // 区域外点击隐藏 $(document).on('click', function(e) { if (!$username.add($menuButton).is(e.target) && $username.has(e.target).length === 0 && $menuButton.has(e.target).length === 0) { $menuButton.hide(); } }); // 智能隐藏逻辑 const elements = $username.add($menuButton); elements.on('mouseleave', function() { hideTimeout = setTimeout(() => { $menuButton.hide(); }, 200); }).on('mouseenter', function() { clearTimeout(hideTimeout); }); var $iframe = $('<iframe>') // 使用 jQuery 监听 iframe 的 load 事件 $iframe.on('load', function() { try { // 确保 iframe 的内容可以被访问(非跨域情况下) var iframeDocument = $(this).contents(); var result = iframeDocument.find('body').text(); // 获取 iframe 内部的内容作为返回值 console.log("Iframe 加载完成后的返回值:", result); window.location.href="/KuCun2/login.html" } catch (error) { console.error("无法访问 iframe 内容,可能是因为跨域限制:", error.message); } }); // 将 iframe 添加到文档中 $('body').append($iframe); }); guanli.js(function ($) { //checkLoginStatus();//权限 https(“/KuCun2/users/guanli/getusers”,null,function(e){ $(“#DataTable”).remove() const c=[{id:" 序号 ", name:“名字”, andy:“账号”, role:“权限” }] let row = e.length+1; //c.concat(b); //const combinedArray = [c,e]; var r=deepMergeArrays(c,e) console.log(r ) let sclass = "table table-hover"; let bodys=[] let table=$("#TableView") table.append(bodys) let te= table.find("table") $.each(r, function(index, value) { var tr="" var boo=false if(index==0){var tr=""; boo=false} tr+="" $.each(value, function(name,val) { if(name!="pass"){ if(name!="id"){ tr+=${val}; }else{ tr+=${val}; } } }) tr+="" if(index==0){ tr+=""} te.append(tr) }); addRight("#DataTable"); // // let sclass = “table table-striped”; // let bodys=[] // // for(let i = -1; i <= row; i++) { // bodys.push(“”); // // if(i=-1){ // // for(var a in c[0]) { // // var bo= “”+c[0][a]+“”; // bodys.push(bo) // // } // // // }else{ // // // for(let j = 0; j <= col; j++) { // if(j == 0) { // var bo = “” + e[i][b[j]] + “#”; // bodys.push(bo) // } else { // var bo= “” // + // // ‘’ // +“”; // bodys.push(bo) // } // } // } // bodys.push( “”); // // } // bodys.push(“”); // var s=bodys.join(‘’) // $(“#TableView”).append(s); // addRight(“#DataTable”); // }) })(jQuery) 登录后访问index.html会被重定向到login.html其他以页面不回,

function https(url,data,fu){ $.ajax({ contentType:"application/json", url: url, // 假设后端 API 地址 method: "POST", data: JSON.stringify(data), type: "json", dataType: "json", success:function(e){ console.log(e.data) if(e.status==200){ console.log(e.data) fu(e.data) }else{ alert(e.text) } }, error: function (e) { console.log(e.data) console.error(e) alert("请求失败,请稍后再试!"+e); } }); } function checkLoginStatus() { if (window.location.href.includes('/login.html')) return; } window.addEventListener('beforeunload', function(e) { console.trace('触发页面跳转的堆栈跟踪:'); }); //安全验证函数 function validateUserSession() { try { // 防御性数据获取 + 数据清洗 const rawValue = localStorage.getItem("name"); const username = String(rawValue ?? '') .trim() .replace(/[\u200B-\u200D\uFEFF]/g, ''); // 移除零宽字符 // 调试信息 console.log('[Auth] Raw:', rawValue, 'Processed:', username); // 复合验证条件 const isValid = ( username.length >= 2 && // 最小长度要求 !/[<>]/.test(username) && // 防止XSS username !== 'null' && username !== 'undefined' ); if (!isValid) { console.warn('无效用户标识:', username); // 安全跳转方法 //window.location.replace('/KuCun2/login.html'); // 立即终止执行 return Promise.reject('Invalid session'); } // 返回清洗后的用户名 return username; } catch (error) { console.error('会话验证失败:', error); // window.location.replace('/KuCun2/login.html'); return Promise.reject(error); } } function deepMergeArrays(frontend, backend) { const resultMap = new Map(); // 遍历前端数据并存入 Map 中以便快速查找 frontend.forEach(item => resultMap.set(item.id, { ...item })); // 遍历后端数据并与前端数据进行合并 backend.forEach(item => { if (resultMap.has(item.id)) { // 如果存在相同 ID,则合并两者的内容 resultMap.set( item.id, Object.assign(resultMap.get(item.id), item) ); } else { // 如果不存在相同 ID,则新增该条目 resultMap.set(item.id, { ...item }); } }); // 将最终结果转回数组形式 return Array.from(resultMap.values()); } (function ($){ // 页面加载时检查登录状态 checkLoginStatus(); })(jQuery); function removeSpecificCharactersAndConvertToNumber(str, charsToRemove) { const regex = new RegExp([${charsToRemove}], 'g'); // 创建用于匹配指定字符的正则表达式 const cleanedStr = str.replace(regex, ''); // 移除指定字符 const numberValue = parseFloat(cleanedStr); // 转换为浮点数 return isNaN(numberValue) ? null : numberValue; // 如果无法解析,则返回 null } <modelVersion>4.0.0</modelVersion> <groupId>KuCun2</groupId> <artifactId>KuCun2</artifactId> <version>0.0.1-SNAPSHOT</version> war <name>KuCun2</name> <description/> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.3.12.RELEASE</version> <relativePath/> <webVersion>4.0</webVersion> UTF-8 3.21.12 <dependencies> <dependency> <groupId>javax</groupId> <artifactId>javaee-api</artifactId> <version>8.0</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.glassfish.web</groupId> <artifactId>javax.servlet.jsp.jstl</artifactId> <version>1.2.4</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.33</version> <exclusions> <exclusion> <groupId>com.google.protobuf</groupId> <artifactId>protobuf-java</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> <scope>provided</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <exclusions> <exclusion> <groupId>com.fasterxml.jackson.datatype</groupId> <artifactId>jackson-datatype-jsr310</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>com.fasterxml.jackson.datatype</groupId> <artifactId>jackson-datatype-jsr310</artifactId> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-core</artifactId> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-annotations</artifactId> </dependency> <dependency> <groupId>org.mindrot</groupId> <artifactId>jbcrypt</artifactId> <version>0.4</version> </dependency> <dependency> <groupId>com.google.protobuf</groupId> <artifactId>protobuf-java</artifactId> <version>${protobuf.version}</version> </dependency> <dependency> <groupId>org.yaml</groupId> <artifactId>snakeyaml</artifactId> <version>1.30</version> </dependency> </dependencies> <build> <artifactId>maven-compiler-plugin</artifactId> <version>2.3.2</version> <configuration> <source>1.8</source> <target>1.8</target> <compilerArgs> <arg>-parameters</arg> </compilerArgs> </configuration> <artifactId>maven-war-plugin</artifactId> <version>2.6</version> <configuration> <failOnMissingWebXml>false</failOnMissingWebXml> </configuration> </build> package com.kucun; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; @SpringBootApplication( ) public class DemoApplication extends SpringBootServletInitializer { public static void main(String[] args) { // // ConfigurableApplicationContext ctx = SpringApplication.run(Application.class, args); // Arrays.stream(ctx.getBeanNamesForType(SecurityFilterChain.class)) // .forEach(System.out::println); // // // // 测试密码加密示例 // BCryptPasswordEncoder encoder = new BCrypt(); // String rawPassword = "987987"; // String encodedPassword = encoder.encode(rawPassword); // System.out.println("加密后的密码:" + encodedPassword); SpringApplication.run(DemoApplication.class, args); } }package com.kucun.Config; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.kucun.Config.Role.RoleConverter; import com.kucun.Config.user.CustomUserDetails; import com.kucun.data.entity.User; import com.kucun.dataDo.UserRepository; /** * 获取数据 * @author Administrator * */ @Service public class CustomUserDetailsService/* implements UserDetailsService */{ // // @Autowired // private UserRepository userRepository; // // @Autowired // private RoleConverter roleConverter; // // public CustomUserDetailsService() { // // super(); // System.out.println("11111"); // } ///** // * 获取数据库中用户信息 // * @param andy 账号 // * // * @return // * // */ // @Override // public UserDetails loadUserByUsername(String andy) { // System.out.println(andy); // User user = userRepository.findByAndy(andy); // System.out.println(user); // return new CustomUserDetails(user, // roleConverter.convert(user.getRole()) // 关键转换点[^1] // ); // } }package com.kucun.Config; // 2. 基础安全配置 //@Configuration //@EnableWebSecurity // 启用Web安全功能 public class SecurityConfig //extends WebSecurityConfigurerAdapter { // @Override // public void configure(WebSecurity web) { // web.ignoring().antMatchers("/check-session"); // } // // 添加自定义Controller // @RestController // public static class SessionCheckController { // @GetMapping("/check-session") // public ResponseEntity<?> checkSession(HttpServletRequest request) { // return request.getSession(false) != null ? // ResponseEntity.ok().build() : // ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); // } // } // /** // * 核心安全过滤器链配置 // * @param http HTTP安全构建器 // * @return 安全过滤器链 // * @throws Exception 配置异常 // * // * █ 配置逻辑说明: // * 1. authorizeHttpRequests: 定义访问控制规则 // * 2. formLogin: 配置表单登录 // * 3. logout: 配置注销行为 // * 4. exceptionHandling: 处理权限异常[^3] // */ // // // 修正后的配置方法 // @Override // protected void configure(HttpSecurity http) throws Exception { // // // // http // .csrf().disable() // .sessionManagement() // .sessionCreationPolicy(SessionCreationPolicy.ALWAYS) // .invalidSessionUrl("/login.html?session=invalid") // .maximumSessions(1) // .maxSessionsPreventsLogin(false) // .and() // .and() // .addFilterBefore(jsonAuthFilter(), UsernamePasswordAuthenticationFilter.class) // 关键配置 // .authorizeRequests() // .antMatchers("/login.html", "/users/login").permitAll() // .antMatchers("/js/**", "/css/**", "/fonts/**", "/images/**","/check-session","/main/bootstrap-3.3.7-dist/**").permitAll() // .antMatchers("/users/guanli/**").hasAuthority("ROLE_ADMIN") // .anyRequest().authenticated() // .and() // .formLogin().disable() //// .loginPage("/login.html") //// .loginProcessingUrl("/users/login") //// //// .successHandler(ajaxAuthenticationSuccessHandler()) // 自定义成功处理器 //// .failureHandler(ajaxAuthenticationFailureHandler()) // 自定义失败处理器 //// .defaultSuccessUrl("/index.html") //// .failureUrl("/login.html?error=true") //// .usernameParameter("andy") // 修改用户名参数名 //// .passwordParameter("pass") // 修改密码参数名 //// .and() // // .logout() // .logoutUrl("/logout") // .logoutSuccessUrl("/login.html") // .and() // .csrf() // .ignoringAntMatchers("/users/login") // .and() // .headers() // .frameOptions().sameOrigin() // .and() // .exceptionHandling() // .accessDeniedHandler(accessDeniedHandler()); // 统一使用Handler // } // // // // // // // 返回JSON格式的成功响应 // @Bean // public AuthenticationSuccessHandler ajaxAuthenticationSuccessHandler() { // return (request, response, authentication) -> { // // // // // 强制创建服务端会话 // request.getSession(true); // // // // // String contextPath = request.getContextPath(); // HttpSession session = request.getSession(true); // Cookie cookie = new Cookie("JSESSIONID", session.getId()); // cookie.setPath(contextPath.isEmpty() ? "/" : contextPath + "/"); // cookie.setMaxAge(1800); // 30分钟 // response.addCookie(cookie); // // // //构建安全响应数据 // Map<String, Object> responseData = new HashMap<>(); // responseData.put("sessionId", request.getSession().getId()); // responseData.put("userInfo",Collections.unmodifiableMap(new HashMap<String, Object>() {/** // * // */ // private static final long serialVersionUID = 1L; // // { // put("Name", ((CustomUserDetails)authentication.getPrincipal()).getName()); // put("role", ((CustomUserDetails)authentication.getPrincipal()).getRole()); // }})); // // // // // 统一返回JSON格式 // response.setContentType(MediaType.APPLICATION_JSON_VALUE); // // new ObjectMapper().writeValue(response.getWriter(), responseData); // // // // // // // // // // response.setContentType(MediaType.APPLICATION_JSON_VALUE); // CustomUserDetails userDetails = (CustomUserDetails) authentication.getPrincipal(); // // response.setStatus(HttpStatus.OK.value()); // System.out.println(authentication.getPrincipal()+""+authentication.getName()); // // if (request.getHeader("X-Requested-With") == null) { // 非AJAX请求 // response.sendRedirect("/index.html"); // } else { // // //String re=userDetails.getUser().toString() // new ObjectMapper().writeValue(response.getWriter(), userDetails.getUser() // ); // // } // // // // // // // }; // } // // // 返回401状态码和错误信息 // @Bean // public AuthenticationFailureHandler ajaxAuthenticationFailureHandler() { // return (request, response, exception) -> { // if (request.getHeader("X-Requested-With") == null) { // response.sendRedirect("/login.html?error=true"); // } else { // response.setStatus(HttpStatus.UNAUTHORIZED.value()); // response.getWriter().write("{\"error\":\"Authentication failed\"}"); // } // }; // } // // // 处理未认证请求 // @Bean // public AuthenticationEntryPoint ajaxAuthenticationEntryPoint() { // return (request, response, exception) -> { // if (request.getHeader("X-Requested-With") == null) { // response.sendRedirect("/login.html?error=true"); // } else { // response.setStatus(HttpStatus.UNAUTHORIZED.value()); // response.getWriter().write("{\"error\":\"Authentication failed\"}"); // } // }; // } // // // // // // @Bean // public JsonUsernamePasswordAuthenticationFilter jsonAuthFilter() throws Exception { // // JsonUsernamePasswordAuthenticationFilter filter = // new JsonUsernamePasswordAuthenticationFilter(); // filter.setAuthenticationManager(authenticationManagerBean()); // filter.setUsernameParameter("andy"); // 设置自定义参数名 // filter.setPasswordParameter("pass"); // filter.setFilterProcessesUrl("/users/login"); // filter.setAuthenticationSuccessHandler(ajaxAuthenticationSuccessHandler()); // filter.setAuthenticationFailureHandler(ajaxAuthenticationFailureHandler()); // // return filter; // } // // // /** // * 密码编码器(必须配置) // * 使用BCrypt强哈希算法加密 // */ // @Bean // public PasswordEncoder passwordEncoder() { // return new BCryptPasswordEncoder(); // } // // // @Bean // public AccessDeniedHandler accessDeniedHandler() { // System.out.println("0000"); // return (request, response, ex) -> { // if (!response.isCommitted()) { // response.sendRedirect("/error/403"); // } // }; // } // // //} // // // // // // //class JsonUsernamePasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter { // private final ObjectMapper objectMapper = new ObjectMapper(); // // @Override // public Authentication attemptAuthentication(HttpServletRequest request, // HttpServletResponse response) // throws AuthenticationException { // System.out.println("收到认证请求,路径:" + request.getRequestURI()); // System.out.println("请求方法:" + request.getMethod()); // System.out.println("Content-Type:" + request.getContentType()); // if (request.getContentType() != null && // request.getContentType().startsWith(MediaType.APPLICATION_JSON_VALUE)) { // // try (InputStream is = request.getInputStream()) { // Map<String, String> authMap = objectMapper.readValue(is, Map.class); // // String username = authMap.getOrDefault(getUsernameParameter(), ""); // String password = authMap.getOrDefault(getPasswordParameter(), ""); // // // 调试日志 // System.out.println("Authentication attempt with: " + username+'_'+ password); // // UsernamePasswordAuthenticationToken authRequest = // new UsernamePasswordAuthenticationToken(username, password); // // setDetails(request, authRequest); // return this.getAuthenticationManager().authenticate(authRequest); // } catch (IOException e) { // throw new AuthenticationServiceException("认证请求解析失败", e); // } // } // Authentication aut= super.attemptAuthentication(request, response); // System.out.println("结果:"+aut.isAuthenticated()); // // return aut; // } } package com.kucun.Config.Role; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.annotation.PostConstruct; import javax.json.Json; import org.springframework.stereotype.Component; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; /** * 权限转化 * @author Administrator * */ @Component public class RoleConverter { // private static final Map<Integer, String> ROLE_MAP = new HashMap<>(); // // @PostConstruct // public void init() { // ROLE_MAP.put(0, "ROLE_ADMIN"); // ROLE_MAP.put(1, "ROLE_USER"); // ROLE_MAP.put(2, "ROLE_MANAGER"); // ROLE_MAP.put(3, "ROLE_AUDITOR"); // } // // public List<GrantedAuthority> convert(int roleCode) { // ObjectMapper mapper = new ObjectMapper(); // try { // System.out.println(mapper.writeValueAsString(Collections.singletonList( // new SimpleGrantedAuthority(ROLE_MAP.getOrDefault(roleCode, "ROLE_GUEST")))).toString());//输出[{"authority":"ROLE_ADMIN"}] // } catch (JsonProcessingException e) { // // TODO Auto-generated catch block // e.printStackTrace(); // } // // return Collections.singletonList( // new SimpleGrantedAuthority(ROLE_MAP.getOrDefault(roleCode, "ROLE_GUEST")) // ); // } // }package com.kucun.Config.user; import java.util.Collection; import com.kucun.data.entity.User; public class CustomUserDetails /*implements UserDetails*/ { // /** // * // */ // private static final long serialVersionUID = 1L; // private final String andy; // 对应andy字段 // private final String name; // private final int role; // private final String password; // private final User users; // private final Collection<? extends GrantedAuthority> authorities; // // public CustomUserDetails(User user, Collection<? extends GrantedAuthority> authorities) { // this.andy = user.getAndy(); // this.name = user.getName(); // this.role = user.getRole(); // this.password = user.getPass(); // user.setPass(null); // this.users=user; // this.authorities = authorities; // } // // // 实现UserDetails接口方法 // @Override public String getUsername() { return andy; } // @Override public String getPassword() { return password; } // @Override public Collection<? extends GrantedAuthority> getAuthorities() { return authorities; } // // // 自定义字段访问方法 // public String getName() { return name; } // public User getUser() { return users; } // public int getRole() { return role; } // // // 其他必要方法 // @Override public boolean isAccountNonExpired() { return true; } // @Override public boolean isAccountNonLocked() { return true; } // @Override public boolean isCredentialsNonExpired() { return true; } // @Override public boolean isEnabled() { return true; } } <!doctype html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"> <title>峤丞板材库存管理</title> <meta name="viewport" content="width=device-width, initial-scale=1"> <style type="text/css"> *{ margin:0; padding:0; } .frame-header { height: 60px; background-color: #23262E; justify-content: space-between; } .frame-header-li{ font-family: Arial, Helvetica, sans-serif; font-size:40px; } .frame-ul{ } .frame-ul li{ border-style: solid; border-width:1px 0px 1px 0px; margin-top: 1px; height: 35px; text-align: center } #username{ position: absolute; right: 0; /* 靠右 */ } .frame-body { position: fixed; top: 60px; right: 0; bottom: 0; left: 0; display: flex; flex-direction: row; } .frame-side { scrollbar-width: none; /* firefox隐藏滚动条 */ -ms-overflow-style: none; /* IE 10+隐藏滚动条 */ overflow-x: hidden; overflow-y: auto; width: 200px; background-color:#9e5; } .frame-side::-webkit-scrollbar { display: none; /* Chrome Safari 隐藏滚动条*/ } .frame-main { flex-grow: 1; background-color:#fff; } .jiaoluo{ margin: auto; margin-right: 0px; } </style> </head> <body> 峤丞木材仓库管理 峤丞木材仓库管理 <button class=".menu-button" style="">注销</button> 首页 材料录入 材料录入 <iframe id="iframeid" name="main" src="main/index.html" width="100%" height="100%" frameborder="0"> </iframe> </body> <script type="text/javascript" src="js/jquery-3.2.1.min.js"></script> <script type="text/javascript" src="js/jsyilai.js"></script> </html><!DOCTYPE html> <html lang="zh-CN"> <head> <title>扁平简洁的登录页面演示_dowebok</title> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> </head> <body> 登 录 <form class="login100-form validate-form"> 用户名 <input class="input100" type="text" name="andy" placeholder="请输入用户名"> 密码 <input class="input100" type="password" name="pass" placeholder="请输入用户密码"> <input class="input-checkbox100" id="ckb1" type="checkbox" name="remember-me"> <label class="label-checkbox100" for="ckb1">记住我</label> 忘记密码? <button class="login100-form-btn">登 录</button> </form> <script type="text/javascript"> </script> <script src="js/jquery-3.2.1.min.js"></script> <script type="text/javascript" src="js/jsyilai.js"></script> </body> </html> yilai.js (function ($) { var pathName = window.location.pathname; console.log(pathName); // 输出类似于 '/index.html' //alert(pathName) switch (pathName){ case "/KuCun2/login.html": jQuery.getScript('/KuCun2/js/main.js?'+Date.now()) jQuery.getScript('/KuCun2/js/login.js?'+Date.now()) break; case "/KuCun2/main/Guanli.html": jQuery.getScript('/KuCun2/js/main.js?'+Date.now()) jQuery.getScript('/KuCun2/js/guanli.js?'+Date.now()) jQuery.getScript('/KuCun2/main/bootstrap-3.3.7-dist/js/FileSaver.js?'+Date.now()) jQuery.getScript('/KuCun2/main/bootstrap-3.3.7-dist/js/MyTable.js?'+Date.now()) break; case "/KuCun2/index.html": jQuery.getScript('/KuCun2/js/main.js?'+Date.now()) jQuery.getScript('/KuCun2/js/index.js?'+Date.now()) break; } })(jQuery) main.js function https(url,data,fu){ $.ajax({ contentType:"application/json", url: url, // 假设后端 API 地址 method: "POST", data: JSON.stringify(data), type: "json", dataType: "json", success:function(e){ console.log(e.data) if(e.status==200){ console.log(e.data) fu(e.data) }else{ alert(e.text) } }, error: function (e) { console.log(e.data) console.error(e) alert("请求失败,请稍后再试!"+e); } }); } function checkLoginStatus() { if (window.location.href.includes('/login.html')) return; } window.addEventListener('beforeunload', function(e) { console.trace('触发页面跳转的堆栈跟踪:'); }); //安全验证函数 function validateUserSession() { try { // 防御性数据获取 + 数据清洗 const rawValue = localStorage.getItem("name"); const username = String(rawValue ?? '') .trim() .replace(/[\u200B-\u200D\uFEFF]/g, ''); // 移除零宽字符 // 调试信息 console.log('[Auth] Raw:', rawValue, 'Processed:', username); // 复合验证条件 const isValid = ( username.length >= 2 && // 最小长度要求 !/[<>]/.test(username) && // 防止XSS username !== 'null' && username !== 'undefined' ); if (!isValid) { console.warn('无效用户标识:', username); // 安全跳转方法 //window.location.replace('/KuCun2/login.html'); // 立即终止执行 return Promise.reject('Invalid session'); } // 返回清洗后的用户名 return username; } catch (error) { console.error('会话验证失败:', error); // window.location.replace('/KuCun2/login.html'); return Promise.reject(error); } } function deepMergeArrays(frontend, backend) { const resultMap = new Map(); // 遍历前端数据并存入 Map 中以便快速查找 frontend.forEach(item => resultMap.set(item.id, { ...item })); // 遍历后端数据并与前端数据进行合并 backend.forEach(item => { if (resultMap.has(item.id)) { // 如果存在相同 ID,则合并两者的内容 resultMap.set( item.id, Object.assign(resultMap.get(item.id), item) ); } else { // 如果不存在相同 ID,则新增该条目 resultMap.set(item.id, { ...item }); } }); // 将最终结果转回数组形式 return Array.from(resultMap.values()); } (function ($){ // 页面加载时检查登录状态 checkLoginStatus(); })(jQuery); function removeSpecificCharactersAndConvertToNumber(str, charsToRemove) { const regex = new RegExp([${charsToRemove}], 'g'); // 创建用于匹配指定字符的正则表达式 const cleanedStr = str.replace(regex, ''); // 移除指定字符 const numberValue = parseFloat(cleanedStr); // 转换为浮点数 return isNaN(numberValue) ? null : numberValue; // 如果无法解析,则返回 null } logi.js/// <reference path="jquery.d.ts" /> // $(document).ready(function(){ // $("#submit").click(function(){ // // // $.ajax({ // url:"../users/login", // async:false, // data:JSON.stringify({ // andy:$("#andy").val(), // pass:$("#pass").val(), // name:$("#name").val()}), // type:"post", // contentType:"application/json", // success:function(e){ // alert(e) // } // }); // // // }); // }) (function ($) { "use strict"; /*================================================================== [ Focus Contact2 ]*/ $('.input100').each(function () { $(this).on('blur', function () { if ($(this).val().trim() != "") { $(this).addClass('has-val'); } else { $(this).removeClass('has-val'); } }) }) /*================================================================== [ Validate ]*/ var input = $('.validate-input .input100'); $('.validate-form').on('submit', function (e) { e.preventDefault(); var check = true; for (var i = 0; i < input.length; i++) { if (validate(input[i]) == false) { showValidate(input[i]); check = false; } } confirm(input) if(check) login(input); return check; }); $('.validate-form .input100').each(function () { $(this).focus(function () { hideValidate(this); }); }); function validate(input) { if ($(input).attr('type') == 'email' || $(input).attr('name') == 'email') { if ($(input).val().trim().match(/^([a-zA-Z0-9_\-\.]+)@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([a-zA-Z0-9\-]+\.)+))([a-zA-Z]{1,5}|[0-9]{1,3})(\]?)$/) == null) { return false; } } else { if ($(input).val().trim() == '') { return false; } } } function showValidate(input) { var thisAlert = $(input).parent(); alert(input) $(thisAlert).addClass('alert-validate'); } function hideValidate(input) { var thisAlert = $(input).parent(); $(thisAlert).removeClass('alert-validate'); } // 登录按钮点击事件 function login(datas) { var data={} datas.each(function(a,element){ data[$(element).attr('name')]=$(element).val() }) alert(data.name); //var data={ andy,pass } // 模拟 AJAX 请求 https("/KuCun2/users/login",data,function (response) { alert("122222"); if (response.name) { localStorage.setItem("name", response.name); // 保存用户名到本地存储 localStorage.setItem("role", response.role); // 保存权限到本地存储 alert( response.name) window.location.href = '/KuCun2/index.html'; } else { alert("登录失败,请检查用户名和密码!"); } }) }; // 注销按钮点击事件 $("#logout-btn").click(function () { localStorage.removeItem("name"); // 清除本地存储中的用户名 checkLoginStatus(); // 更新登录状态 }); })(jQuery);index.js$().ready(function () { const username = localStorage.getItem("name"); $("#username").text(username) const $username = $('#username'); const $menuButton = $('.menu-button'); let hideTimeout = null; // 点击显示按钮 $username.on('click', function(e) { e.stopPropagation(); $menuButton.show(); }); // 区域外点击隐藏 $(document).on('click', function(e) { if (!$username.add($menuButton).is(e.target) && $username.has(e.target).length === 0 && $menuButton.has(e.target).length === 0) { $menuButton.hide(); } }); // 智能隐藏逻辑 const elements = $username.add($menuButton); elements.on('mouseleave', function() { hideTimeout = setTimeout(() => { $menuButton.hide(); }, 200); }).on('mouseenter', function() { clearTimeout(hideTimeout); }); var $iframe = $('<iframe>') // 使用 jQuery 监听 iframe 的 load 事件 $iframe.on('load', function() { try { // 确保 iframe 的内容可以被访问(非跨域情况下) var iframeDocument = $(this).contents(); var result = iframeDocument.find('body').text(); // 获取 iframe 内部的内容作为返回值 console.log("Iframe 加载完成后的返回值:", result); window.location.href="/KuCun2/login.html" } catch (error) { console.error("无法访问 iframe 内容,可能是因为跨域限制:", error.message); } }); // 将 iframe 添加到文档中 $('body').append($iframe); }); guanli.js(function ($) { //checkLoginStatus();//权限 https("/KuCun2/users/guanli/getusers",null,function(e){ $("#DataTable").remove() const c=[{id:" 序号 ", name:"名字", andy:"账号", role:"权限" }] let row = e.length+1; //c.concat(b); //const combinedArray = [c,e]; var r=deepMergeArrays(c,e) console.log(r ) let sclass = "table table-hover"; let bodys=[] let table=$("#TableView") table.append(bodys) let te= table.find("table") $.each(r, function(index, value) { var tr="" var boo=false if(index==0){var tr=""; boo=false} tr+="" $.each(value, function(name,val) { if(name!="pass"){ if(name!="id"){ tr+=${val}; }else{ tr+=${val}; } } }) tr+="" if(index==0){ tr+=""} te.append(tr) }); addRight("#DataTable"); // // let sclass = "table table-striped"; // let bodys=[] // // for(let i = -1; i <= row; i++) { // bodys.push(""); // // if(i=-1){ // // for(var a in c[0]) { // // var bo= ""+c[0][a]+""; // bodys.push(bo) // // } // // // }else{ // // // for(let j = 0; j <= col; j++) { // if(j == 0) { // var bo = "<td><span>" + e[i][b[j]] + "#"; // bodys.push(bo) // } else { // var bo= "" // + // // '' // +""; // bodys.push(bo) // } // } // } // bodys.push( ""); // // } // bodys.push(""); // var s=bodys.join('') // $("#TableView").append(s); // addRight("#DataTable"); // }) })(jQuery) 访问index.html会被重定向到login.html其他以页面不回

<template> <Search @location-selected="handleLocationSelected" /> <LocationBar v-if="loaded" :update-interval="100" :use-dms-format="useDmsFormat" /> </template> <style> /* @import "/temp/css/divGraphic.css"; */ </style> <script setup lang="ts"> import { computed, onUnmounted, onMounted, reactive } from "vue"; import LocationBar from "./location-bar.vue"; import Search from "./search.vue"; import initMap from "./init"; import { ref } from "vue"; import { throttle } from 'lodash'; import { loadRipplePoints, createMultipleRippleCircles } from './circle.js'; import { $prototype } from "../../main.ts"; const miniMapContainer = ref<HTMLElement>(); let viewIndicator: Rectangle; // 视图指示器样式 const currentPosition = reactive({ longitude: 113.361538, latitude: 27.339318, }); // 株洲市范围常量 const ZHUZHOU_EXTENT = { west: 112.5, // 株洲市西边界 east: 114.5, // 株洲市东边界 south: 26.0, // 株洲市南边界 north: 28.0 // 株洲市北边界 }; const rippleEntities = ref<any[]>([]); // 存储波纹圆实体 const heightThreshold = 80000; // 高度阈值(米),高于此值隐藏波纹圆 // 修复1:重新定义 indicatorStyle 为 ref const indicatorStyle = ref({ left: '50%', top: '50%', display: "block" }); const updateRippleVisibility = throttle(() => { if (!$prototype.$map || rippleEntities.value.length === 0) return; // 修复1:将 shouldShow 提取到块级作用域外部 let shouldShow = false; // 获取相机高度 const cartographic = $prototype.$map.camera.positionCartographic; // 修复2:添加空值检查 if (cartographic) { const cameraHeight = cartographic.height; shouldShow = cameraHeight > heightThreshold; } // 修复3:确保 shouldShow 已定义 rippleEntities.value.forEach(entity => { entity.show = shouldShow; }); }, 200); // 更新指示器位置 const updateIndicatorPosition = () => { if (!$prototype.$map) return; const camera = $prototype.$map.camera; const rect = camera.computeViewRectangle(); if (!rect) return; // 计算在株洲市范围内的相对位置 const center = Cesium.Rectangle.center(rect); const lon = Cesium.Math.toDegrees(center.longitude); const lat = Cesium.Math.toDegrees(center.latitude); // 确保位置在株洲市范围内 const constrainedLon = Math.max(ZHUZHOU_EXTENT.west, Math.min(ZHUZHOU_EXTENT.east, lon)); const constrainedLat = Math.max(ZHUZHOU_EXTENT.south, Math.min(ZHUZHOU_EXTENT.north, lat)); const lonPercent = ((constrainedLon - ZHUZHOU_EXTENT.west) / (ZHUZHOU_EXTENT.east - ZHUZHOU_EXTENT.west)) * 100; const latPercent = 100 - ((constrainedLat - ZHUZHOU_EXTENT.south) / (ZHUZHOU_EXTENT.north - ZHUZHOU_EXTENT.south)) * 100; // 更新CSS指示器 indicatorStyle.value = { left: ${lonPercent}%, top: ${latPercent}%, display: "block" }; }; // 更新鹰眼地图 - 只更新指示器位置 const updateOverview = () => { if (!$prototype.$map || !overviewViewer.value) return; // 获取主地图的当前视图中心 const rectangle = $prototype.$map.camera.computeViewRectangle(); if (!rectangle) return; // 更新指示器位置 updateIndicatorPosition(); }; const overviewViewer = ref(null); // 初始化鹰眼地图 - 使用全球底图+株洲市叠加层 const initMiniMap = () => { Cesium.Ion.defaultAccessToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiIxMDhlNDdmYy03NzFhLTQ1ZTQtOWQ3NS1lZDAzNDc3YjE4NDYiLCJpZCI6MzAxNzQyLCJpYXQiOjE3NDcwNTMyMDN9.eaez8rQxVbPv2LKEU0sMDclPWyHKhh1tR27Vg-_rQSM"; if (!miniMapContainer.value) return; // 创建全球底图提供器 const worldProvider = new Cesium.UrlTemplateImageryProvider({ url: "https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}", minimumLevel: 0, maximumLevel: 19, tilingScheme: new Cesium.WebMercatorTilingScheme(), }); // 创建株洲市专用影像提供器 const zhuzhouProvider = new Cesium.UrlTemplateImageryProvider({ url: "http://124.232.190.30:9000/proxy/pk1725866655224/map/zzzsyx_18/{z}/{x}/{y}.png", minimumLevel: 1, maximumLevel: 17, tilingScheme: new Cesium.WebMercatorTilingScheme(), }); // 鹰眼地图初始化 - 使用全球底图 overviewViewer.value = new Cesium.Viewer(miniMapContainer.value, { sceneMode: Cesium.SceneMode.SCENE2D, baseLayerPicker: false, homeButton: false, timeline: false, navigationHelpButton: false, animation: false, scene3DOnly: true, selectionIndicator: false, infoBox: false, imageryProvider: worldProvider, // 使用全球底图 terrainProvider: undefined, mapProjection: new Cesium.WebMercatorProjection(), skyBox: false, skyAtmosphere: false }); // 添加株洲市影像作为叠加层 const zhuzhouLayer = overviewViewer.value.imageryLayers.addImageryProvider(zhuzhouProvider); // 设置固定视野为株洲市范围 overviewViewer.value.camera.setView({ destination: Cesium.Rectangle.fromDegrees( ZHUZHOU_EXTENT.west, ZHUZHOU_EXTENT.south, ZHUZHOU_EXTENT.east, ZHUZHOU_EXTENT.north ) }); // 隐藏控件 const toolbar = overviewViewer.value.container.getElementsByClassName("cesium-viewer-toolbar")[0]; if (toolbar) toolbar.style.display = "none"; // 隐藏版权信息 overviewViewer.value.cesiumWidget.creditContainer.style.display = "none"; // 初始化视图指示器 initRectangle(); }; // 初始化视图指示器 function initRectangle() { // 创建视图指示器(株洲市范围框) viewIndicator = overviewViewer.value.entities.add({ rectangle: { coordinates: Cesium.Rectangle.fromDegrees( ZHUZHOU_EXTENT.west, ZHUZHOU_EXTENT.south, ZHUZHOU_EXTENT.east, ZHUZHOU_EXTENT.north ), material: Cesium.Color.RED.withAlpha(0.3), outline: true, outlineColor: Cesium.Color.RED, outlineWidth: 2, }, }); } const loaded = ref(false); const useDmsFormat = ref(false); function addDemoGraphics() { const chinaBoundary = $prototype.$map.dataSources.add( Cesium.GeoJsonDataSource.load("/shp_zz.geojson", { stroke: Cesium.Color.WHITE, fill: false, clampToGround: true, describe: null, }) ); chinaBoundary.then((dataSource) => { const entities = dataSource.entities.values; for (let entity of entities) { if (entity.polyline) { entity.polyline.fill = false; } } }); } function flyToDes() { const center = Cesium.Cartesian3.fromDegrees(-98.0, 40.0); $prototype.$map.camera.flyTo({ destination: new Cesium.Cartesian3( -2432812.6687511606, 5559483.804371395, 2832009.419525571 ), orientation: { heading: 6.283185307179421, pitch: -1.0472145569408116, roll: 6.2831853071795205, }, complete: function () {}, }); } // 监听主地图相机变化 const setupCameraListener = () => { $prototype.$map.camera.changed.addEventListener(updateOverview); }; // 鹰眼地图点击处理 const handleMiniMapClick = (event: MouseEvent) => { if (!miniMapContainer.value || !overviewViewer.value) return; const rect = miniMapContainer.value.getBoundingClientRect(); const x = event.clientX - rect.left; const y = event.clientY - rect.top; // 计算在固定范围内的相对位置 const xPercent = (x / rect.width) * 100; const yPercent = (y / rect.height) * 100; // 转换为株洲市范围内的经纬度 const lon = ZHUZHOU_EXTENT.west + (xPercent / 100) * (ZHUZHOU_EXTENT.east - ZHUZHOU_EXTENT.west); const lat = ZHUZHOU_EXTENT.north - (yPercent / 100) * (ZHUZHOU_EXTENT.north - ZHUZHOU_EXTENT.south); // 主地图飞向点击位置 $prototype.$map.camera.flyTo({ destination: Cesium.Cartesian3.fromDegrees(lon, lat, 1000000), }); }; function addImage() { var rightImageProvider = new Cesium.UrlTemplateImageryProvider({ name: "影像图", type: "xyz", layer: "vec_d", url: "http://124.232.190.30:9000/proxy/pk1725866655224/map/zzzsyx_18/{z}/{x}/{y}.png", minimumLevel: 1, maximumLevel: 17, crs: "EPSG:3857", }); $prototype.$map.imageryLayers.addImageryProvider(rightImageProvider); rightImageProvider.splitDirection = Cesium.SplitDirection.right; } onMounted(() => { initMap(); addImage(); loaded.value = true; addDemoGraphics(); flyToDes(); // 修复6:确保正确的初始化顺序 setTimeout(() => { initMiniMap(); setupCameraListener(); // 初始更新 updateOverview(); }, 1000); (async () => { try { const ripplePoints = await loadRipplePoints(); rippleEntities.value = createMultipleRippleCircles( $prototype.$map, ripplePoints ); $prototype.$map.camera.changed.addEventListener(updateRippleVisibility); updateRippleVisibility(); } catch (error) { console.error('加载波纹圆失败:', error); } })(); }); let currentMarker: any = null; const createMarker = (location: { lng: number; lat: number; name: string }) => { if (currentMarker) { $prototype.$map.entities.remove(currentMarker); } currentMarker = $prototype.$map.entities.add({ position: Cesium.Cartesian3.fromDegrees( location.lng, location.lat, 50 ), point: { pixelSize: 200, color: Cesium.Color.RED, outlineColor: Cesium.Color.WHITE, outlineWidth: 2, heightReference: Cesium.HeightReference.RELATIVE_TO_GROUND, }, label: { text: location.name, font: "40px sans-serif", fillColor: Cesium.Color.WHITE, outlineColor: Cesium.Color.BLACK, outlineWidth: 1, verticalOrigin: Cesium.VerticalOrigin.BOTTOM, pixelOffset: new Cesium.Cartesian2(0, -20), heightReference: Cesium.HeightReference.RELATIVE_TO_GROUND, distanceDisplayCondition: new Cesium.DistanceDisplayCondition(0, 10000), }, description: ${location.name} 经度:${location.lng.toFixed(6)} 纬度:${location.lat.toFixed(6)} , }); return currentMarker; }; const handleLocationSelected = (location: { lng: number; lat: number; name: string; }) => { if (!$prototype.$map) return; const destination = Cesium.Cartesian3.fromDegrees( location.lng, location.lat, 200 ); $prototype.$map.camera.flyTo({ destination, orientation: { heading: Cesium.Math.toRadians(0), pitch: Cesium.Math.toRadians(-45), roll: 0, }, duration: 2, complete: () => { createMarker(location); }, }); }; onUnmounted(() => { if ($prototype.$map) { $prototype.$map.destroy(); $prototype.$map = null; } if ($prototype.$map) { $prototype.$map.camera.changed.removeEventListener(updateRippleVisibility); } console.log("组件销毁"); }); const emit = defineEmits(["onload", "onclick"]); const initMars3d = async (option: any) => { emit("onclick", true); emit("onload", $prototype.$map); }; </script> <style lang="less"> /**cesium 工具按钮栏*/ .cesium-viewer-toolbar { top: auto !important; bottom: 35px !important; left: 12px !important; right: auto !important; } .cesium-toolbar-button img { height: 100%; } .cesium-viewer-toolbar > .cesium-toolbar-button, .cesium-navigationHelpButton-wrapper, .cesium-viewer-geocoderContainer { margin-bottom: 5px; float: left; clear: both; text-align: center; } .cesium-button { background-color: rgba(23, 49, 71, 0.8); color: #e6e6e6; fill: #e6e6e6; box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3); line-height: 32px; } .cesium-button:hover { background: #3ea6ff; } /**cesium 底图切换面板*/ .cesium-baseLayerPicker-dropDown { bottom: 0; left: 40px; max-height: 700px; margin-bottom: 5px; background-color: rgba(23, 49, 71, 0.8); } /**cesium 帮助面板*/ .cesium-navigation-help { top: auto; bottom: 0; left: 40px; transform-origin: left bottom; background: none; background-color: rgba(23, 49, 71, 0.8); .cesium-navigation-help-instructions { background: none; } .cesium-navigation-button { background: none; } .cesium-navigation-button-selected, .cesium-navigation-button-unselected:hover { background: rgba(0, 138, 255, 0.2); } } /**cesium 二维三维切换*/ .cesium-sceneModePicker-wrapper { width: auto; } .cesium-sceneModePicker-wrapper .cesium-sceneModePicker-dropDown-icon { float: right; margin: 0 3px; } /**cesium POI查询输入框*/ .cesium-viewer-geocoderContainer .search-results { left: 0; right: 40px; width: auto; z-index: 9999; } .cesium-geocoder-searchButton { background-color: rgba(23, 49, 71, 0.8); } .cesium-viewer-geocoderContainer .cesium-geocoder-input { background-color: rgba(63, 72, 84, 0.7); } .cesium-viewer-geocoderContainer .cesium-geocoder-input:focus { background-color: rgba(63, 72, 84, 0.9); } .cesium-viewer-geocoderContainer .search-results { background-color: rgba(23, 49, 71, 0.8); } /**cesium info信息框*/ .cesium-infoBox { top: 50px; background-color: rgba(23, 49, 71, 0.8); } .cesium-infoBox-title { background-color: rgba(23, 49, 71, 0.8); } /**cesium 任务栏的FPS信息*/ .cesium-performanceDisplay-defaultContainer { top: auto; bottom: 35px; right: 50px; } .cesium-performanceDisplay-ms, .cesium-performanceDisplay-fps { color: #fff; } /**cesium tileset调试信息面板*/ .cesium-viewer-cesiumInspectorContainer { top: 10px; left: 10px; right: auto; } .cesium-cesiumInspector { background-color: rgba(23, 49, 71, 0.8); } /**覆盖mars3d内部控件的颜色等样式*/ .mars3d-compass .mars3d-compass-outer { fill: rgba(23, 49, 71, 0.8); } .mars3d-contextmenu-ul, .mars3d-sub-menu { background-color: rgba(23, 49, 71, 0.8); > li > a:hover, > li > a:focus, > li > .active { background-color: #3ea6ff; } > .active > a, > .active > a:hover, > .active > a:focus { background-color: #3ea6ff; } } /* Popup样式*/ .mars3d-popup-color { color: #ffffff; } .mars3d-popup-background { background: rgba(23, 49, 71, 0.8); } .mars3d-popup-content { margin: 15px; } .mars3d-template-content label { padding-right: 6px; } .mars3d-template-titile { border-bottom: 1px solid #3ea6ff; } .mars3d-template-titile a { font-size: 16px; } .mars3d-tooltip { background: rgba(23, 49, 71, 0.8); border: 1px solid rgba(23, 49, 71, 0.8); } .mars3d-popup-btn-custom { padding: 3px 10px; border: 1px solid #209ffd; background: #209ffd1c; } .mars-dialog .mars-dialog__content { height: 100%; width: 100%; overflow: auto; padding: 5px; } .image { border: solid 2px #fff; } .content { height: 90%; padding-top: 10px; overflow-x: auto; overflow-y: auto; } .content-text { padding: 0 10px; text-indent: 30px; font-size: 17px; } .details-video { width: 100%; height: 760px; background-color: #000; } :where(.css-lt97qq9).ant-space { display: inline-flex; } :where(.css-lt97qq9).ant-space-align-center { align-items: center; } :where(.css-lt97qq9).ant-image .ant-image-mask { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; color: #fff; background: rgba(0, 0, 0, 0.5); cursor: pointer; opacity: 0; transition: opacity 0.3s; } :where(.css-lt97qq9).ant-image .ant-image-mask .ant-image-mask-info { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; padding: 0 4px; } :where(.css-1t97qq9)[class^="ant-image"] [class^="ant-image"], :where(.css-1t97qq9)[class*=" ant-image"] [class^="ant-image"], :where(.css-1t97qq9)[class^="ant-image"] [class*=" ant-image"], :where(.css-1t97qq9)[class*=" ant-image"] [class*=" ant-image"] { box-sizing: border-box; } :where(.css-lt97qq9).ant-image .ant-image-img { width: 100%; height: auto; vertical-align: middle; } </style> <style scoped> .mini-map-container { position: relative; width: 100%; height: 100%; } .main-viewer { width: 100%; height: 100%; } .mini-map { position: absolute; right: 3vw; bottom: 6vh; width: 12vw; height: 17vh; border: 2px solid #fff; box-shadow: 0 0 10px rgba(0, 0, 0, 0.5); z-index: 999; cursor: pointer; overflow: hidden; } .location-indicator { position: absolute; width: 14px; height: 14px; background: #ff3e3e; border: 2px solid white; border-radius: 50%; transform: translate(-50%, -50%); box-shadow: 0 0 15px rgba(255, 62, 62, 1); z-index: 100; } .view-rectangle { position: absolute; border: 2px solid #00f2fe; z-index: 99; pointer-events: none; box-shadow: 0 0 20px rgba(0, 242, 254, 0.7); } /* 相机聚焦样式 - 四个角折角 */ .corner { position: absolute; width: 25px; height: 25px; z-index: 100; pointer-events: none; } .corner::before, .corner::after { content: ""; position: absolute; background: #00f2fe; box-shadow: 0 0 10px rgba(0, 242, 254, 0.8); } .corner-top-left { top: -2px; left: -2px; } .corner-top-left::before { top: 0; left: 0; width: 15px; height: 3px; } .corner-top-left::after { top: 0; left: 0; width: 3px; height: 15px; } .corner-top-right { top: -2px; right: -2px; } .corner-top-right::before { top: 0; right: 0; width: 15px; height: 3px; } .corner-top-right::after { top: 0; right: 0; width: 3px; height: 15px; } .corner-bottom-left { bottom: -2px; left: -2px; } .corner-bottom-left::before { bottom: 0; left: 0; width: 15px; height: 3px; } .corner-bottom-left::after { bottom: 0; left: 0; width: 3px; height: 15px; } .corner-bottom-right { bottom: -2px; right: -2px; } .corner-bottom-right::before { bottom: 0; right: 0; width: 15px; height: 3px; } .corner-bottom-right::after { bottom: 0; right: 0; width: 3px; height: 15px; } .camera-icon { position: absolute; top: 10px; right: 10px; color: #00f2fe; font-size: 24px; z-index: 100; text-shadow: 0 0 10px rgba(0, 242, 254, 0.8); } .focus-effect { position: absolute; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; z-index: 98; border: 2px solid rgba(0, 242, 254, 0.2); border-radius: 5px; box-shadow: inset 0 0 30px rgba(0, 242, 254, 0.1); } .pulse { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 10px; height: 10px; border-radius: 50%; background: rgba(0, 242, 254, 0.7); box-shadow: 0 0 0 0 rgba(0, 242, 254, 0.7); animation: pulse 2s infinite; } ::v-deep.cesium-viewer-toolbar { display: none; } </style> 如上代码,新新的要求,那个株洲市影像图代码你帮我注释掉,先不用了,改而在鹰眼地图上展示就和主地图一样的那个株洲市白色边界即可,基于此修改一下代码,尽可能减少对vue中其他无关代码的修改

# https://search.dangdang.com/?key=%B1%E0%B3%CC&act=input&page_index=1 # 导入requests import requests import time from bs4 import BeautifulSoup import mysql.connector import csv # 定义容器 用来存储所有数据 allContainer = [] for i in range(1, 36): # 判断当前是否为第一次循环 if i == 1: url = "https://search.dangdang.com/?key=%B1%E0%B3%CC&act=input&page_index=1" else: url = f"https://search.dangdang.com/?key=%B1%E0%B3%CC&act=input&page_index={i}" print(f"当前已完成第{i}次") # 循环休眠 防止检测 time.sleep(1) # 发起请求 # 请求头 header = { "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Accept-Encoding": "gzip, deflate, br, zstd", "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6", "Cache-Control": "max-age=0", "Connection": "keep-alive", "Cookie": "ddscreen=2; __permanent_id=20250609184530979156760224679480468; __visit_id=20250609184530993130404124438448889; __out_refer=1749465931%7C!%7Cwww.baidu.com%7C!%7C; dest_area=country_id%3D9000%26province_id%3D111%26city_id%3D0%26district_id%3D0%26town_id%3D0; __rpm=s_112100.155956512835%2C155956512836..1749466159510%7Cs_112100.155956512835%2C155956512836..1749466166450; search_passback=1e0bf85a587c99ab37bc4668fc0100003945670025bc4668; __trace_id=20250609184927332100480187110221325", "Host": "search.dangdang.com", "Referer": "https://search.dangdang.com/?key=%B1%E0%B3%CC&act=input&page_index=2", "Sec-Fetch-Dest": "document", "Sec-Fetch-Mode": "navigate", "Sec-Fetch-Site": "same-origin", "Sec-Fetch-User": "?1", "Upgrade-Insecure-Requests": "1", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36 Edg/137.0.0.0", } response = requests.get(url, headers=header) # 设置响应的编码格式 # response.encoding = 'utf-8' # 自动识别编码方式(关键!) response.encoding = response.apparent_encoding # 将响应先保存至本地,然后先测试对本地页面进行数据解析 然后再进行多次爬取 # with open('../data/当当网.html', 'w', encoding='utf-8') as f: # f.write(response.text) htmlTree = BeautifulSoup(response.text, 'html.parser') allulEle = htmlTree.find_all('ul', class_="bigimg") for ul in allulEle: # 根据每一个ul标签中的li 进行指定数据的获取 allw1 = ul.find_all('li', recursive=False) # 获取w1下的p标签 for li_tag in allw1: rowContainer = [] # 提取书名 title_tag = li_tag.find_all('p', class_='name') if title_tag: a_tag = li_tag.find_all('a') if a_tag: title = a_tag[0].get('title') href = a_tag[0].get('href') link = f"https:{href}" rowContainer.append(title) rowContainer.append(link) else: title = "" href = "" else: title = "" href = "" pre_price = li_tag.find_all('span', class_='search_pre_price') for p in pre_price: PrePrice = p.get_text(strip=True) rowContainer.append(PrePrice) # 提取评论数 comment_count = li_tag.find('a', class_='search_comment_num') if comment_count: CommentCount = comment_count.get_text(strip=True) else: CommentCount = '0条评论' rowContainer.append(CommentCount) # 提取作者、出版时间、出版社 author_info = li_tag.find('p', class_='search_book_author') for p in author_info: AuthorInfo = p.get_text(strip=True).replace('\\\\', '').replace('/', '') if not AuthorInfo: AuthorInfo = '' rowContainer.append(AuthorInfo) allContainer.append(rowContainer) for i in allContainer: print(i) # 导入数据库模块 import mysql.connector # 使用当前库中的内置对象来创建数据库连接 mydb = mysql.connector.connect( host='localhost', # 当前mysql运行服务的地址 port=3306, # mysql服务的端口号 user='root', # mysql用户名 password='root', # 密码 database='dangdang' ) # 创建游标对象 mycursor = mydb.cursor() # discount VARCHAR ( 20 ), -- 折扣 # 创建图书信息表 create_table_sql = """ CREATE TABLE IF NOT EXISTS books ( id INT AUTO_INCREMENT PRIMARY KEY, title VARCHAR ( 255 ) NOT NULL, -- 书名 link VARCHAR ( 512 ), -- 链接 now_price VARCHAR ( 20 ), -- 现价 comment_count VARCHAR ( 50 ), -- 评论数 author VARCHAR ( 100 ), -- 作者 publish_date VARCHAR ( 20 ), -- 出版时间 publisher VARCHAR ( 100 ), -- 出版社 action VARCHAR ( 100 ), unidentified VARCHAR ( 20 ) ) """ # 执行建表语句 mycursor.execute(create_table_sql) # 插入语句 insert_sql = """ INSERT INTO books (title, link, now_price, comment_count, author, publish_date, publisher, action, unidentified) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s) """ for book in allContainer: if len(book) == 8: book.insert(4, '') mycursor.execute(insert_sql, list(book)) # 提交事务 mydb.commit() print("✅ 数据插入完成,共插入", len(allContainer), "条记录") # 关闭连接 mycursor.close() mydb.close() import pandas as pd # 转成 DataFrame df = pd.DataFrame(allContainer, columns=["书名", "链接", "现价", "评论数", "作者", "出版时间", "出版社", "可选状态", "未知"]) # 插入序号列(从 1 开始) df.insert(0, '序号', range(1, len(df) + 1)) # 保存为 Excel 文件 df.to_excel("../data/当当网.xlsx", index=False) print("✅ 数据已成功保存为 Excel 文件!") import jieba import wordcloud # 读取excel 文件到当前代码中 import openpyxl from wordcloud import WordCloud # 获取当前excel 表格对象 wb = openpyxl.load_workbook('../data/当当网.xlsx') # 获取当前表格中的sheet sheet = wb.worksheets[0] # 遍历当前的execl 对象 # min row = 2 代表的是从当前表格中的第二行开始获取 # min col = 3 代表获取第三列 # max col = 3 最大的列是三,之确保我们获取当前第三列 # 定义一个列表用来存储当前获取的所有的数据 data = [] for row in sheet.iter_rows(min_row=2, min_col=2, max_col=5): data.append(row[0].value) # 获取每个单元格中的value值 # print(data) # 对当前的数组中的元素进行分割词组 seg_list = jieba.cut(''.join(data), cut_all=False) # print(type(seg_list)) # # print('/'.join(seg_list)) # 引入当前的字体 作为词云图的渲染字体 fonts = '../data/AlibabaPuHuiTi-2-65-Medium.ttf' wc = WordCloud( # 通过属性对当前的词云图进行赋值 width=1200, # 宽600px height=600, background_color='white', max_font_size=50, min_font_size=10, font_path=fonts ) # 将分隔完成的数据 加载到当前的wc对象中 wc.generate_from_text(''.join(seg_list)) # 保存当前的结果到指定文件夹中 wc.to_file("../data/词云图.png") import numpy as np import pandas as pd # 这些设置有助于调试时查看完整的 DataFrame 数据,适合开发阶段使用 pd.set_option('display.max_columns', None) pd.set_option('display.max_rows', None) pd.set_option('display.width', None) pd.set_option('display.max_colwidth', None) newXml = pd.read_excel("../data/当当网.xlsx") print(newXml.shape) # 查看行数列数 print(newXml.info()) # 查看各列的数据类型及缺失值 # # 检查重复行 duplicates = newXml.duplicated(keep='first') print("重复行数量:", duplicates.sum()) # # 删除重复行 cleaned_data = newXml.drop_duplicates() print("删除重复后数据形状:", cleaned_data.shape) # # 删除含有空值的行 dropna_data = newXml.dropna() print("删除空值后数据形状:", dropna_data.shape) # 或者填充空值 # filled_data = newXml.fillna({"未知": "默认值"}) df = newXml.drop(columns=['未知']) print(df) # filled_data = newXml.fillna({"CPU信息": "未知", "等级": 0}) df.to_excel("../data/new当当.xlsx", index=False) import pandas as pd import numpy as np import pandas as pd from pyecharts import options as opts from pyecharts.charts import Bar, Line, Scatter, Pie, Radar from pyecharts.globals import ThemeType # 读取文件 excel_file = pd.ExcelFile('../data/new当当.xlsx') # 获取指定工作表中的数据 df = excel_file.parse('Sheet1') # 将出版社列转换为字符串类型 df['出版社'] = df['出版社'].astype(str) # 获取出版社和书名列的数据 publishers = df['出版社'].to_numpy() book_names = df['书名'].to_numpy() # 获取唯一的出版社 unique_publishers = np.unique(publishers) # 统计每个出版社的书籍数量 book_counts = np.array([np.sum(publishers == publisher) for publisher in unique_publishers]) # 构建结果 DataFrame result_df = pd.DataFrame({ '出版社': unique_publishers, '书籍数量': book_counts }) print(result_df) # 读取数据 df = pd.read_excel('../data/new当当.xlsx') # 数据预处理 # 转换现价列,提取数字 df['现价'] = df['现价'].str.extract('(\d+\.?\d*)').astype(float) # 转换评论数列,提取数字 df['评论数'] = df['评论数'].str.extract('(\d+)').astype(int) # 转换出版时间列,提取年份 df['出版年份'] = pd.to_datetime(df['出版时间']).dt.year # 图表1:价格分布直方图 hist, bins = pd.cut(df['现价'], bins=20, retbins=True) hist_value = hist.value_counts().sort_index() # 使用 Bar 来模拟直方图 histogram = ( Bar(init_opts=opts.InitOpts(theme=ThemeType.LIGHT, width="800px", height="400px")) .add_xaxis([f"{bins[i]:.2f}-{bins[i + 1]:.2f}" for i in range(len(bins) - 1)]) .add_yaxis("书籍数量", hist_value.tolist(), category_gap=0) .set_global_opts( title_opts=opts.TitleOpts(title="价格分布柱状图"), xaxis_opts=opts.AxisOpts(name="价格区间"), yaxis_opts=opts.AxisOpts(name="数量"), ) ) # 图表2:不同出版社出版书籍数量柱状图 publisher_counts = df['出版社'].value_counts() bar_publisher = ( Bar(init_opts=opts.InitOpts(theme=ThemeType.LIGHT, width="800px", height="400px")) .add_xaxis(publisher_counts.index.tolist()) .add_yaxis("出版书籍数量", publisher_counts.tolist()) .set_global_opts( title_opts=opts.TitleOpts(title="不同出版社出版书籍数量柱状图"), xaxis_opts=opts.AxisOpts(name="出版社", axislabel_opts={"rotate": 90}), yaxis_opts=opts.AxisOpts(name="出版书籍数量"), ) ) # 图表3:每年出版书籍数量折线图 yearly_counts = df['出版年份'].value_counts().sort_index() line_yearly = ( Line(init_opts=opts.InitOpts(theme=ThemeType.LIGHT, width="800px", height="400px")) .add_xaxis(yearly_counts.index.astype(str).tolist()) .add_yaxis("出版书籍数量", yearly_counts.tolist(), is_smooth=True, symbol="circle") .set_global_opts( title_opts=opts.TitleOpts(title="每年出版书籍数量折线图"), xaxis_opts=opts.AxisOpts(name="出版年份"), yaxis_opts=opts.AxisOpts(name="出版书籍数量"), ) ) # 图表4:评论数前五书籍的书名与评论数柱状图 top_5_commented = df.nlargest(5, '评论数') bar_comment = ( Bar(init_opts=opts.InitOpts(theme=ThemeType.LIGHT, width="800px", height="400px")) .add_xaxis(top_5_commented['书名'].tolist()) .add_yaxis("评论数", top_5_commented['评论数'].tolist()) .set_global_opts( title_opts=opts.TitleOpts(title="评论数前五书籍的书名与评论数柱状图"), xaxis_opts=opts.AxisOpts(name="书名", axislabel_opts={"rotate": 90}), yaxis_opts=opts.AxisOpts(name="评论数"), ) ) # 图表5:价格与评论数的散点图 # 将现价列转换为字符串类型 df['现价'] = df['现价'].astype(str) # 提取价格数值 df['价格'] = df['现价'].str.extract(r'(\d+\.?\d*)').astype(float) # 检查价格列是否存在缺失值 print(f"价格列缺失值数量: {df['价格'].isna().sum()}") # 删除价格列为缺失值的行 df = df.dropna(subset=['价格']) # 定义价格区间 bins = [0, 50, 100, 150, 200, float('inf')] labels = ['0 - 50', '51 - 100', '101 - 150', '151 - 200', '200以上'] # 划分价格区间并统计数量 df['价格区间'] = pd.cut(df['价格'], bins=bins, labels=labels) price_range_counts = df['价格区间'].value_counts().reset_index(name='数量') # 使用 pyecharts 绘制饼状图 pie = ( Pie() .add( series_name="数量", data_pair=[list(z) for z in zip(price_range_counts['价格区间'], price_range_counts['数量'])], radius=["40%", "75%"], ) .set_global_opts( title_opts=opts.TitleOpts(title="价格区间与数量的饼状图"), legend_opts=opts.LegendOpts(orient="vertical", pos_top="15%", pos_left="2%"), ) .set_series_opts( label_opts=opts.LabelOpts(formatter="{b}: {d}%") ) ) # 将评论数列转换为字符串类型 df['评论数'] = df['评论数'].astype(str) # 提取评论数数值 df['评论数数值'] = df['评论数'].str.extract(r'(\d+\.?\d*)').astype(float) # 找出评论数前五的书籍 top_5_books = df.nlargest(5, '评论数数值', keep='all')[['书名', '评论数数值']] # 定义雷达图的指标 c_schema = [{"name": book_name, "max": top_5_books['评论数数值'].max()} for book_name in top_5_books['书名']] # 准备雷达图的数据 data = [[count for count in top_5_books['评论数数值'].values]] # 创建雷达图对象 ( Radar() .add_schema(schema=c_schema) .add( series_name="评论数", data=data, areastyle_opts=opts.AreaStyleOpts(opacity=0.2) ) .set_global_opts( title_opts=opts.TitleOpts(title="评论数前五的书籍的书名与评论数雷达图"), ) .render("../data/radar_chart_top5_books.html") ) # 统计不同出版社的书籍数量 publisher_book_count = df['出版社'].value_counts().reset_index() publisher_book_count.columns = ['出版社', '书籍数量'] # 选取书籍数量前 10 的出版社 top_10_publisher = publisher_book_count.nlargest(10, '书籍数量') # 创建散点图对象 scatter = ( Scatter() .add_xaxis(top_10_publisher['出版社'].tolist()) .add_yaxis( series_name="书籍数量", y_axis=top_10_publisher['书籍数量'].tolist(), symbol_size=10, label_opts=opts.LabelOpts(is_show=False) ) .set_global_opts( title_opts=opts.TitleOpts(title="不同出版社书籍数量前10的散点图"), xaxis_opts=opts.AxisOpts( name="出版社", type_="category", axislabel_opts=opts.LabelOpts(rotate=45, interval="auto") ), yaxis_opts=opts.AxisOpts(name="书籍数量"), ) ) # 保存图表 histogram.render("../data/price_distribution_histogram.html") bar_publisher.render("../data/publisher_book_count_bar.html") line_yearly.render("../data/yearly_book_count_line.html") bar_comment.render("../data/top_commented_books_bar.html") pie.render("../data/price_range_pie_chart.html") scatter.render("../data/scatter_top10_publisher_book_count.html") from flask import Flask, request, render_template_string, jsonify import requests # import requests # # # 定义一个message的变量,作为会话的容器 # messages = [{"role":"system","content":""}] # # # API KEY # API_KEY = "sk-ec2e933afb424766ba6bce9765960a3a" # # 设置请求头 # header = { # "Content-Type": "application/json", # 告知服务器我们传递的内容的数据类型 # "Authorization": f"Bearer {API_KEY}" # api_key # } # # # 请求地址 # url = "https://api.deepseek.com/chat/completions" # # # 因为要完成多轮对话 所以要有循环 # # 同时因为要完成用户的多次对话请求 # # def DeepSeekChat(userMessage): # # 1. 将用户输入的信息与角色进行拼接 从而变成一个完成的对话 # messages.append( {"role": "user", "content": userMessage}) # # # 2. 请求deepseek 请求过程中将我们携带的多个参数进行传递 # data = { # "model":"deepseek-chat", # "messages":messages, # "stream":False # } # # # 3. 发起请求 # response = requests.post(url, json=data, headers=header) # # # 4. 对response进行处理 # if response.status_code == 200: # # 获取响应内容 # result_json = response.json() # # 处理当前json中的内容 # airesult = result_json['choices'][0]['message']['content'] # # AI返回结果 # print(f"图书商城AI客服:{airesult}") # # 如何实现多轮对话 # # 将回复的内容继续追加到列表中,形成会话的闭合,结合上下文内容 # messages.append({"role": "assistant", "content": airesult}) # else: # print(response.text) # print("请求失败") # # print("图书商城欢迎您") # print("使用exit退出程序") # # while True: # userinput = input("你:") # if userinput == "exit": # print("退出当前程序") # break # else: # # 调用函数完成多次请求的发送 # # 将用户输入的内容作为参数调用函数 完成API的调用 # DeepSeekChat(userinput) 这是我写的代码,需要把爬虫得到的数据与deepseek相结合,使得可以在一个新页面上根据数据与ai对话,请你进行修改

import os import re import math import http.server import socketserver import urllib.parse from collections import defaultdict from lxml import etree, html # 配置信息 PORT = 8000 DOCUMENTS_DIR = "documents" # 存放HTML文件的目录 INDEX_FILE = "search_index.json" # 创建文档目录(如果不存在) os.makedirs(DOCUMENTS_DIR, exist_ok=True) # 示例文档(实际应用中会从文件系统加载) sample_docs = [ { "id": 1, "title": "Python编程语言", "content": "Python是一种高级编程语言,由Guido van Rossum创建。它强调代码可读性和简洁的语法。", "url": "doc1.html" }, { "id": 2, "title": "lxml库简介", "content": "lxml是Python中处理XML和HTML的强大库,基于libxml2和libxslt库构建。", "url": "doc2.html" }, { "id": 3, "title": "构建搜索网站", "content": "使用Python和lxml可以构建高效的搜索系统,解析HTML并提取关键信息。", "url": "doc3.html" } ] # 创建示例文档 for doc in sample_docs: with open(os.path.join(DOCUMENTS_DIR, doc["url"]), "w", encoding="utf-8") as f: f.write(f""" <!DOCTYPE html> <html> <head> <title>{doc['title']}</title> </head> <body> {doc['title']} {doc['content']} 相关主题: Python, 编程, lxml, 搜索系统 </body> </html> """) class SearchEngine: def __init__(self): self.index = defaultdict(dict) # 倒排索引 {词: {文档ID: 词频}} self.documents = {} # 文档元数据 {文档ID: {标题, url, content}} self.doc_count = 0 self.doc_lengths = {} # 文档长度(词数) def tokenize(self, text): """简单的分词函数""" words = re.findall(r'\b\w+\b', text.lower()) return words def build_index(self, documents_dir): """构建搜索索引""" self.index.clear() self.documents.clear() self.doc_count = 0 # 遍历文档目录中的所有HTML文件 for filename in os.listdir(documents_dir): if filename.endswith(".html"): doc_id = self.doc_count + 1 self.doc_count += 1 filepath = os.path.join(documents_dir, filename) # 使用lxml解析HTML with open(filepath, "r", encoding="utf-8") as f: content = f.read() tree = html.fromstring(content) # 提取文档内容 title = tree.findtext(".//title") or filename body_text = " ".join(tree.xpath("//body//text()")) # 存储文档元数据 self.documents[doc_id] = { "title": title.strip(), "url": filename, "content": body_text.strip()[:200] + "..." # 摘要 } # 分词并更新索引 words = self.tokenize(title + " " + body_text) self.doc_lengths[doc_id] = len(words) # 更新倒排索引 for word in set(words): # 使用set避免重复计数 if doc_id not in self.index[word]: self.index[word][doc_id] = 0 self.index[word][doc_id] += words.count(word) def tf_idf(self, term, doc_id): """计算TF-IDF分数""" # 词频(TF) tf = self.index[term].get(doc_id, 0) / self.doc_lengths[doc_id] # 逆文档频率(IDF) idf = math.log(self.doc_count / (1 + len(self.index[term]))) return tf * idf def search(self, query): """执行搜索查询""" if not self.index: self.build_index(DOCUMENTS_DIR) query_terms = self.tokenize(query) scores = defaultdict(float) # 计算每个文档的相关性分数 for term in query_terms: if term in self.index: for doc_id in self.index[term]: score = self.tf_idf(term, doc_id) scores[doc_id] += score # 按分数排序 sorted_results = sorted(scores.items(), key=lambda x: x[1], reverse=True) # 准备结果 results = [] for doc_id, score in sorted_results: if score > 0: # 只返回有相关性的文档 doc_info = self.documents[doc_id].copy() doc_info["score"] = round(score, 4) results.append(doc_info) return results # 创建搜索引擎实例 search_engine = SearchEngine() class SearchHandler(http.server.SimpleHTTPRequestHandler): def do_GET(self): """处理GET请求""" if self.path == "/": # 显示搜索页面 self.send_response(200) self.send_header("Content-type", "text/html; charset=utf-8") self.end_headers() html_content = self.generate_search_page() self.wfile.write(html_content.encode("utf-8")) elif self.path.startswith("/search?"): # 处理搜索请求 query = urllib.parse.parse_qs(urllib.parse.urlparse(self.path).query).get("q", [""])[0] self.send_response(200) self.send_header("Content-type", "text/html; charset=utf-8") self.end_headers() results = search_engine.search(query) html_content = self.generate_results_page(query, results) self.wfile.write(html_content.encode("utf-8")) else: # 处理静态文件请求 super().do_GET() def generate_search_page(self): """生成搜索页面""" return f""" <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Python搜索网站</title> <style> body {{ font-family: Arial, sans-serif; margin: 0; padding: 20px; background-color: #f5f5f5; }} .container {{ max-width: 800px; margin: 0 auto; background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }} h1 {{ color: #2c3e50; text-align: center; }} .search-box {{ display: flex; margin: 20px 0; }} input[type="text"] {{ flex: 1; padding: 12px; font-size: 16px; border: 1px solid #ddd; border-radius: 4px 0 0 4px; }} button {{ padding: 12px 20px; background: #3498db; color: white; border: none; border-radius: 0 4px 4px 0; cursor: pointer; font-size: 16px; }} button:hover {{ background: #2980b9; }} .result-item {{ margin: 15px 0; padding: 15px; border: 1px solid #eee; border-radius: 4px; }} .result-title {{ font-size: 18px; font-weight: bold; margin-bottom: 5px; color: #3498db; }} .result-url {{ color: #006621; font-size: 14px; margin-bottom: 5px; }} .result-snippet {{ color: #545454; }} .no-results {{ text-align: center; padding: 20px; color: #777; }} .footer {{ margin-top: 30px; text-align: center; color: #777; font-size: 14px; }} </style> </head> <body> Python搜索网站 <form action="/https/wenku.csdn.net/search" method="get"> <input type="text" name="q" placeholder="输入搜索关键词..."> <button type="submit">搜索</button> </form> 这个搜索网站使用Python内置库和lxml构建,可以索引和搜索本地HTML文档。 示例文档已包含在系统中,尝试搜索: "Python", "lxml", "搜索"等关键词。 使用Python + lxml构建 | 本地搜索系统 </body> </html> """ def generate_results_page(self, query, results): """生成搜索结果页面""" results_html = "" if results: for result in results: results_html += f""" {result['title']} /{DOCUMENTS_DIR}/{result['url']} {result['content']} 相关性: {result['score']} """ else: results_html = '没有找到相关结果' return f""" <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>搜索: {query} - Python搜索网站</title> <style> body {{ font-family: Arial, sans-serif; margin: 0; padding: 20px; background-color: #f5f5f5; }} .container {{ max-width: 800px; margin: 0 auto; background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }} h1 {{ color: #2c3e50; }} .search-box {{ display: flex; margin: 20px 0; }} input[type="text"] {{ flex: 1; padding: 12px; font-size: 16px; border: 1px solid #ddd; border-radius: 4px 0 0 4px; }} button {{ padding: 12px 20px; background: #3498db; color: white; border: none; border-radius: 0 4px 4px 0; cursor: pointer; font-size: 16px; }} button:hover {{ background: #2980b9; }} .results-count {{ color: #777; margin-bottom: 20px; }} .result-item {{ margin: 15px 0; padding: 15px; border: 1px solid #eee; border-radius: 4px; }} .result-title {{ font-size: 18px; font-weight: bold; margin-bottom: 5px; color: #3498db; }} .result-url {{ color: #006621; font-size: 14px; margin-bottom: 5px; }} .result-snippet {{ color: #545454; }} .result-info {{ color: #777; font-size: 14px; margin-top: 5px; }} .no-results {{ text-align: center; padding: 20px; color: #777; }} </style> </head> <body> Python搜索网站 <form action="/https/wenku.csdn.net/search" method="get"> <input type="text" name="q" value="{query}"> <button type="submit">搜索</button> </form> 找到 {len(results)} 条结果 {results_html} 返回首页 | 使用Python + lxml构建 </body> </html> """ # 启动服务器 with socketserver.TCPServer(("", PORT), SearchHandler) as httpd: print(f"服务器运行在端口 {PORT}") print(f"访问地址: http://localhost:{PORT}/") print("按Ctrl+C停止服务器") try: httpd.serve_forever() except KeyboardInterrupt: print("\n服务器已停止")让浏览器成为专业AI工具,无需字典列表元组集合,还有网络请求,无需外部库,加上后的完整代码

我在web开发中jquery3.7.1使用了history.pushState,但是在前进后退时触发了A soft navigation has been detected $(document).ready(function() { // 监听 popstate 事件,处理浏览器前进后退 window.addEventListener('popstate', function(event) { //const urlParams = new URLSearchParams(window.location.search); //const selectedUrl = urlParams.get('page'); const windUrl = layui.url(); const currentPage = windUrl.search.page; if(currentPage){ // 获取对应页面的 DOM 元素 var pageElement = $('[data-url="' + currentPage + '"]'); }else{ // 没有page参数 var pageElement = $('[data-id="1"]'); } update_sidebar_status(pageElement); // 如果找到了对应的元素,提取数据并更新页面 if (pageElement.length > 0) { const pageData = { id: pageElement.data('id'), item: pageElement.data('item'), url: pageElement.data('url') }; update_page(pageData); } }); }); /* 加载完毕动画 */ function viu_loading() { setTimeout(function() { $('body').addClass('viu-loaded'); setTimeout(function() { $('.viu-loader-overlay').remove(); $('body').removeClass('viu-loaded'); }, 500); }, 500); } /* 移动端检测 */ function is_mobile() { var windowWidth = $(window).width(); if (windowWidth <= 767) { return true; } else if (windowWidth >= 768) { return false; } } /* PC端执行逻辑 */ function pc() { if (!is_mobile()) { // 屏幕宽度大于等于768px时的逻辑 $('.viu-menu-toggle .viu-icon').addClass('viu-active'); $('.viu-wrap').addClass('viu-active'); $('.viu-sidebar').addClass('viu-active'); } } // 检测.viu-active状态 function is_active(e) { return e.hasClass('viu-active'); } // 更新侧边栏状态 function update_sidebar_status(e) { const $e = e; const $li = $e.parent(); const $parentLi = $li.closest('.viu-sidebar-li'); const $subUl = $parentLi.find('.viu-subSidebar-ul'); const $activeSubLi = $subUl.find('.viu-subSidebar-li.viu-active').closest('.viu-sidebar-li'); const $i = $parentLi.find('a > .viu-icon'); const li = $('.viu-sidebar-li'); const i = $('.viu-sidebar-li>a>.viu-icon'); const subUl = $('.viu-subSidebar-ul'); const subLi = $('.viu-subSidebar-li'); const activeSubLi = subUl.find('.viu-subSidebar-li.viu-active').closest('.viu-sidebar-li'); $li.addClass('viu-active'); // 检测是否有二级菜单 if ($subUl.length > 0) { // 折叠所有二级菜单除了当前Ul subUl.not($subUl).slideUp(); // 展开 const isSubUlOpen = $subUl.is(':visible'); const isSubLi = $li.hasClass('viu-subSidebar-li'); if(activeSubLi.length > 0){ li.not(activeSubLi).not($parentLi).removeClass('viu-active'); }else{ li.not($parentLi).removeClass('viu-active'); } // 判断当前点击是否二级菜单 if (!isSubLi) { i.not($i).removeClass('viu-active'); if (!isSubUlOpen) { $subUl.slideDown(); $i.addClass('viu-active'); } else { $subUl.slideUp(); $i.removeClass('viu-active'); if ($activeSubLi.length == 0) { $parentLi.removeClass('viu-active'); } } } else { // 移除所有状态 li.not($parentLi).removeClass('viu-active'); i.not($i).removeClass('viu-active'); subLi.not($li).removeClass('viu-active'); $parentLi.addClass('viu-active'); $i.addClass('viu-active'); $subUl.slideDown(); } } else { // 移除所有状态 li.not($li).removeClass('viu-active'); i.removeClass('viu-active'); subLi.removeClass('viu-active'); subUl.slideUp(); } } // 内容区加载动画 function viu_content_loading(e) { const loading = e.html($('').addClass('viu-content-loader')); const inner = $('').addClass('viu-loader-content-line-scale'); for (let i = 0; i < 5; i++) { const viuItem = $(''); inner.append(viuItem); } $('.viu-content-loader').append(inner).append('正在加载中'); } // 面包屑字符串替换 function processString(input) { // 检查输入是否包含 / if (input.includes('/')) { // 使用 / 分割字符串 const parts = input.split('/'); // 提取第一部分作为标题 const title = parts.shift(); // 移除并返回第一部分 // 将标题用 包裹 const wrappedTitle = ${title}; // 将剩余部分的 / 替换为图标 const remainingPart = parts.join('/').replace(/\//g, ''); // 返回最终结果 return wrappedTitle + remainingPart; } else { // 如果没有 /,直接返回原始字符串 return ${input}; } } // 更新页面url function update_page_url(d){ const windUrl = layui.url(); const currentPage = windUrl.search.page; if (currentPage && currentPage != d.url) { const newUrl = ${windUrl.pathname}?page=${d.url}; // 构建新的 URL history.pushState({}, document.title, newUrl); } else if (!currentPage && d.id !== 1) { const newUrl = ${windUrl.pathname}?page=${d.url}; // 构建新的 URL history.pushState({}, document.title, newUrl); } } // 更新页面 function update_page(d) { const sidebarA = $('.viu-sidebar-li a[data-url="' + d.url + '"]'); if (sidebarA.length > 0) { update_sidebar_status(sidebarA); } const t = processString(d.item); const box = $('#viu-content'); viu_content_loading(box); // 更新面包屑导航 $('.viu-breadcrumb').html(t); // 发送 AJAX 请求获取内容 $.ajax({ url: d.url, data: d, type: 'GET', dataType: 'html', success: function(response) { // 获取成功,显示返回的内容 update_page_url(d); box.html(response); box.data('page', d.url); }, error: function(xhr, textStatus, errorThrown) { // 获取失败,显示 error 控制器返回的内容 $.ajax({ url: '/viu_error', data: d, type: 'GET', dataType: 'html', success: function(errorResponse) { update_page_url(d); box.html(errorResponse); box.data('page', 'error'); }, error: function() { // 如果 error 控制器请求也失败,可以显示一个默认错误信息 update_page_url(d); box.html('获取数据失败,请稍后再试。'); box.data('page', 'error'); } }); } }); } // 初始化页面 function initPage() { const windUrl = layui.url(); const page = windUrl.search.page; let sidebarA = $('.viu-sidebar-li a[data-url="' + page + '"]'); const parentId = sidebarA.data('parentid'); let id, item, url; if (page) { if (sidebarA.length > 0) { if (parentId) { $('.viu-sidebar-li a[data-id="' + parentId + '"]').parent().addClass('viu-active'); } id = sidebarA.data('id'); item = sidebarA.data('item'); url = sidebarA.data('url'); } else { id = -1; item = "页面丢失"; url = page; } } else { sidebarA = $('.viu-sidebar-li a[data-id="1"]'); id = sidebarA.data('id'); item = sidebarA.data('item'); url = sidebarA.data('url'); } if (id) { var datas = { id: id, item: item, url: url }; update_page(datas); } } // 遮罩层对象 const viuMask = { maskElement: null, show(data = '') { // 检查遮罩层是否已经存在,如果不存在则创建它 let maskElement = $('.viu-mask'); if (maskElement.length === 0) { // 创建遮罩层元素 maskElement = $(''); $('body').append(maskElement); } maskElement.show(); }, hide(b = false) { const mask = $('.viu-mask'); if (!b) { // 如果 b 为 true,则直接移除遮罩层 mask.remove(); } else { // 获取 data-class 中存储的类名字符串 const dataClass = mask.data('class'); if (dataClass) { // 分割类名字符串为数组 const classNames = dataClass.split(','); // 遍历类名数组,移除对应元素的 .viu-active 类 classNames.forEach(className => { $('.' + className.trim()).removeClass('viu-active'); }); } // 可选择隐藏遮罩层,如果需要完全移除遮罩层可以调用 mask.remove(); mask.remove(); } } };

import chardet import streamlit as st import pandas as pd import numpy as np import joblib import os import time import matplotlib.pyplot as plt import seaborn as sns from pyspark.sql import SparkSession from pyspark.ml.feature import VectorAssembler, StandardScaler from pyspark.ml.classification import LogisticRegression, DecisionTreeClassifier, RandomForestClassifier from pyspark.ml.evaluation import BinaryClassificationEvaluator from pyspark.ml.tuning import ParamGridBuilder, CrossValidator from sklearn.metrics import classification_report, confusion_matrix import warnings import dask.dataframe as dd from dask.diagnostics import ProgressBar from dask_ml.preprocessing import StandardScaler as DaskStandardScaler import tempfile import shutil warnings.filterwarnings("ignore") plt.rcParams['font.sans-serif'] = ['SimHei'] plt.rcParams['axes.unicode_minus'] = False # 页面设置 st.set_page_config( page_title="单宽转融用户预测系统", page_icon="📶", layout="wide", initial_sidebar_state="expanded" ) # 自定义CSS样式 st.markdown(""" <style> .stApp { background: linear-gradient(135deg, #极f5f7fa 0%, #e4edf5 100%); font-family: 'Helvetica Neue', Arial, sans-serif; } .header { background: linear-gradient(90deg, #2c3e50 0%, #4a6491 100%); color: white; padding: 1.5rem; border-radius: 0.75rem; box-shadow: 0 4px 12px rgba(0,0,0,0.1); margin-bottom: 2rem; } .card { background: white; border-radius: 0.75rem; padding: 1.5rem; margin-bottom: 1.5rem; box-shadow: 0 4px 12px rgba(0,0,0,0.08); transition: transform 0.3s ease; } .card:hover { transform: translateY(-5px); box-shadow: 0 6px 16px rgba(0,0,0,0.12); } .stButton button { background: linear-gradient(90deg, #3498db 0%, #1a5276 100%) !important; color: white !important; border: none !important; border-radius: 0.5rem; padding: 0.75rem 1.5rem; font-size: 1rem; font-weight: 600; transition: all 0.3s ease; width: 100%; } .stButton button:hover { transform: scale(1.05); box-shadow: 0 4px 8px rgba(52, 152, 219, 0.4); } .feature-box { background: linear-gradient(135deg, #e3f2fd 0%, #bbdefb 100%); border-radius: 0.75rem; padding: 1.5rem; margin-bottom: 1.5rem; } .result-box { background: linear-gradient(135deg, #e8f5e9 0%, #c8e6c9 100%); border-radius: 0.75rem; padding: 1.5rem; margin-top: 1.5rem; } .model-box { background: linear-gradient(135deg, #fff3e0 0%, #ffe0b2 100%); border-radius: 0.75rem; padding: 1.5rem; margin-top: 1.5rem; } .stProgress > div > div > div { background: linear-gradient(90deg, #2ecc71 0%, #27ae60 100%) !important; } .metric-card { background: white; border-radius: 0.75rem; padding: 1rem; text-align: center; box-shadow: 0 4px 8px rgba(0,0,0,0.06); } .metric-value { font-size: 1.8rem; font-weight: 700; color: #2c3e50; } .metric-label { font-size: 0.9rem; color: #7f8c8d; margin-top: 0.5rem; } .highlight { background: linear-gradient(90deg, #ffeb3b 0%, #fbc02d 100%); padding: 0.2rem 0.5rem; border-radius: 0.25rem; font-weight: 600; } .stDataFrame { border-radius: 0.75rem; box-shadow: 0 4px 8px rgba(0,0,0,0.06); } .risk-high { background-color: #ffcdd2 !important; color: #c62828 !important; font-weight: 700; } .risk-medium { background-color: #fff9c4 !important; color: #f57f17 !important; font-weight: 600; } .risk-low { background-color: #c8e6c9 !important; color: #388e3c !important; } </style> """, unsafe_allow_html=True) def preprocess_data(ddf): """使用Dask进行大数据预处理""" processed_ddf = ddf.copy() # 删除无意义特征 drop_cols = ['BIL_MONTH', 'ASSET_ROW_ID', 'CCUST_ROW_ID', 'BELONG_CITY', 'MKT_CHANNEL_NAME', 'MKT_CHANNEL_SUB_NAME', 'PREPARE_FLG', 'SERV_START_DT', 'COMB_STAT_NAME', 'FIBER_ACCESS_CATEGORY'] existing_cols = [col for col in drop_cols if col in processed_ddf.columns] processed_ddf = processed_ddf.drop(columns=existing_cols) # 处理缺失值 numeric_cols = processed_ddf.select_dtypes(include=[np.number]).columns.tolist() if 'is_rh_next' in numeric_cols: numeric_cols.remove('is_rh_next') # 识别需要转换为数值型的列 potential_numeric_cols = [ 'MAX_PRICE_COMPANY', 'MAX_PRICE_MODEL', 'MAX_PRICE_TERM_TYPE', 'MOBLE_4G_CNT_LV', 'MOBLE_CNT_LV', 'OWE_AMT_LV', 'OWE_CNT_LV', 'PROM_INTEG_ID', 'TOUSU_CNT_LV' ] # 尝试将潜在数值列转换为数值型 for col in potential_numeric_cols: if col in processed_ddf.columns: try: # 尝试转换为数值型 processed_ddf[col] = processed_ddf[col].astype(float) numeric_cols.append(col) except: # 如果转换失败,则保留为字符串类型 st.warning(f"无法将列 {col} 转换为数值型,保留为字符串类型") processed_ddf[col] = processed_ddf[col].astype(str) with ProgressBar(): means = processed_ddf[numeric_cols].mean().compute() for col in numeric_cols: processed_ddf[col] = processed_ddf[col].fillna(means[col]) # 类型转换 for col in numeric_cols: if processed_ddf[col].dtype == 'float64': if processed_ddf[col].dropna().apply(lambda x: x == int(x)).all(): processed_ddf[col] = processed_ddf[col].astype('Int64') else: processed_ddf[col] = processed_ddf[col].astype('float64') object_cols = processed_ddf.select_dtypes(include=['object']).columns.tolist() for col in object_cols: processed_ddf[col] = processed_ddf[col].fillna("Unknown") # 离散特征编码 binary_cols = ['IF_YHTS', 'is_kdts', 'is_itv_up', 'is_mobile_up', 'if_zzzw_up'] for col in binary_cols: if col in processed_ddf.columns: processed_ddf[col] = processed_ddf[col].map({'否': 0, '是': 1, 0: 0, 1: 1, 'Unknown': -1}) if 'GENDER' in processed_ddf.columns: gender_mapping = {'男': 0, '女': 1, 'Unknown': -1} processed_ddf['GENDER'] = processed_ddf['GENDER'].map(gender_mapping) if 'MKT_STAR_GRADE_NAME' in processed_ddf.columns: star_mapping = {'五星级': 5, '四星级': 4, '三星级': 3, '二星级': 2, '一星级': 1, 'Unknown': 0} processed_ddf['MKT_STAR_GRADE_NAME'] = processed_ddf['MKT_STAR_GRADE_NAME'].map(star_mapping) # 特征工程 if 'PROM_AMT' in processed_ddf.columns and 'STMT_AMT' in processed_ddf.columns: processed_ddf['CONSUMPTION_RATIO'] = processed_ddf['PROM_AMT'] / (processed_ddf['STMT_AMT'] + 1) if 'DWN_VOL' in processed_ddf.columns and 'ONLINE_DAY' in processed_ddf.columns: processed_ddf['TRAFFIC_DENSITY'] = processed_ddf['DWN_VOL'] / (processed_ddf['ONLINE_DAY'] + 1) if 'TERM_CN极T' in processed_ddf.columns: processed_ddf['HAS_TERMINAL'] = (processed_ddf['TERM_CNT'] > 0).astype(int) # 标准化处理 scaler = DaskStandardScaler() numeric_cols_for_scaling = [col for col in numeric_cols if col != 'is_rh_next'] if numeric_cols_for_scaling: processed_ddf[numeric_cols_for_scaling] = scaler.fit_transform(processed_ddf[numeric_cols_for_scaling]) feature_cols = [col for col in processed_ddf.columns if col != 'is_rh_next'] return processed_ddf, feature_cols, means, numeric_cols_for_scaling, scaler def create_spark_session(): """创建或获取现有的Spark会话""" return SparkSession.builder \ .appName("SingleToMeltUserPrediction") \ .config("spark.sql.shuffle.partitions", "8") \ .config("spark.driver.memory", "8g") \ .config("spark.executor.memory", "8g") \ .getOrCreate() def train_models(spark_df, feature_cols): """使用Spark训练多个模型并评估性能""" spark = create_spark_session() assembler = VectorAssembler(inputCols=feature_cols, outputCol="rawFeatures") assembled_df = assembler.transform(spark_df) scaler = StandardScaler(inputCol="rawFeatures", outputCol="features") scaler_model = scaler.fit(assembled_df) scaled_df = scaler_model.transform(assembled_df) train_df, test_df = scaled_df.randomSplit([0.8, 0.2], seed=42) # 定义模型和参数网格 models = { "逻辑回归": ( LogisticRegression(featuresCol="features", labelCol="is_rh_next"), ParamGridBuilder().addGrid(LogisticRegression.regParam, [0.01, 0.1]) .addGrid(LogisticRegression.elasticNetParam, [0.0, 0.5]) .build() ), "决策树": ( DecisionTreeClassifier(featuresCol="features", labelCol="is_rh_next"), ParamGridBuilder().addGrid(DecisionTreeClassifier.maxDepth, [5, 10]) .addGrid(DecisionTreeClassifier.minInstancesPerNode, [10, 20]) .build() ), "随机森林": ( RandomForestClassifier(featuresCol="features", labelCol="is_rh_next", numTrees=10), ParamGridBuilder().addGrid(RandomForestClassifier.numTrees, [10, 20]) .addGrid(RandomForestClassifier.maxDepth, [5, 10]) .build() ) } evaluator = BinaryClassificationEvaluator(labelCol="is_rh_next", metricName="areaUnderROC") results = {} for model_name, (model, param_grid) in models.items(): with st.spinner(f"正在训练{model_name}模型..."): cv = CrossValidator(estimator=model, estimatorParamMaps=param_grid, evaluator=evaluator, numFolds=3) cv_model = cv.fit(train_df) predictions = cv_model.transform(test_df) auc = evaluator.evaluate(predictions) accuracy = predictions.filter(predictions.is_rh_next == predictions.prediction).count() / test_df.count() results[model_name] = { "model": cv_model, "auc": auc, "accuracy": accuracy, "best_params": cv_model.bestModel._java_obj.parent().extractParamMap(), "feature_importances": getattr(cv_model.bestModel, "featureImportances", {}).toArray().tolist() if model_name != "逻辑回归" else None } return results # 页面布局 st.markdown(""" 单宽转融用户预测系统 基于大数据挖掘的精准营销分析平台 """, unsafe_allow_html=True) col1, col2 = st.columns([1, 1.5]) with col1: st.markdown(""" 📈 系统功能 用户转化预测 多模型对比分析 特征重要性分析 可视化数据洞察 """, unsafe_allow_html=True) st.image("https://images.unsplash.com/photo-1550751822256-00808c92fc8d?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1200&q=80", caption="精准营销示意图", use_column_width=True) with col2: option = st.radio("", ["🚀 训练新模型 - 使用新数据训练预测模型", "🔍 模型分析 - 查看现有模型的分析结果"], index=0, label_visibility="hidden") if "训练新模型" in option: st.markdown("模型训练上传训练数据并训练新的预测模型", unsafe_allow_html=True) train_file = st.file_uploader("上传训练数据 (CSV格式)", type=["csv"], accept_multiple_files=False) if train_file is not None: try: with tempfile.TemporaryDirectory() as tmpdir: tmp_path = os.path.join(tmpdir, "large_file.csv") with open(tmp_path, "wb") as f: f.write(train_file.getvalue()) def detect_encoding(file_path): with open(file_path, 'rb') as f: raw_data = f.read(10000) result = chardet.detect(raw_data) return result['encoding'] detected_encoding = detect_encoding(tmp_path) st.info(f"检测到文件编码: {detected_encoding}") chunksize = 256 * 1024 * 1024 na_values_list = ['', '#N/A', '#N/A N/A', '#NA', '-1.#IND', '-1.#QNAN', '-NaN', '-nan', '1.#IND', '1.#QNAN', '<NA>', 'N/A', 'NA', 'NULL', 'NaN', 'n/a', 'nan', 'null'] # 定义特殊列的数据类型 special_dtypes = { 'MAX_PRICE_COMPANY': 'object', 'MAX_PRICE_MODEL': 'object', 'MAX_PRICE_TERM_TYPE': 'object', 'MOBLE_4G_CNT_LV': 'object', 'MOBLE_CNT_LV': 'object', 'OWE_AMT_LV': 'object', 'OWE_CNT_LV': 'object', 'PROM_INTEG_ID': 'object', 'TOUSU_CNT_LV': 'object', 'is_rh_next': 'float64' } # 尝试读取文件 try: raw_ddf = dd.read_csv( tmp_path, blocksize=chunksize, dtype=special_dtypes, encoding=detected_encoding, na_values=na_values_list, assume_missing=True, low_memory=False ) except UnicodeDecodeError: st.warning("检测编码读取失败,尝试GB18030编码...") try: raw_ddf = dd.read_csv( tmp_path, blocksize=chunksize, dtype=special_dtypes, encoding='GB18030', na_values=na_values_list, assume_missing=True, low_memory=False ) except UnicodeDecodeError: st.warning("GB18030读取失败,尝试Latin-1编码...") raw_ddf = dd.read_csv( tmp_path, blocksize=chunksize, dtype=special_dtypes, encoding='latin-1', na_values=na_values_list, assume_missing=True, low_memory=False ) except Exception as e: st.error(f"读取文件时发生错误: {str(e)}") st.stop() with st.expander("数据预览", expanded=True): try: # 使用compute()获取前1000行 preview_data = raw_ddf.head(1000) st.dataframe(preview_data) col1, col2 = st.columns(2) try: total_rows = raw_ddf.shape[0].compute() col1.metric("总样本数", f"{total_rows:,}") except: col1.metric("总样本数", "计算中...") col2.metric("特征数量", len(raw_ddf.columns)) if 'is_rh_next' not in raw_ddf.columns: st.warning("⚠️ 注意:未找到目标变量 'is_rh_next'") else: st.info(f"目标变量类型: {raw_ddf['is_rh_next'].dtype}") except Exception as e: st.error(f"数据预览错误: {str(e)}") st.write("尝试显示前50行...") try: preview_data = raw_ddf.head(50) st.dataframe(preview_data) except: st.error("无法显示数据预览") if st.button("开始数据预处理", use_container_width=True): with st.spinner("正在进行数据预处理,请稍候..."): processed_ddf, feature_cols, means, numeric_cols_for_scaling, scaler = preprocess_data(raw_ddf) preprocessor_params = { 'means': means, 'numeric_cols_for_scaling': numeric_cols_for_scaling, 'scaler': scaler, 'feature_cols': feature_cols } joblib.dump(preprocessor_params, 'preprocessor_params.pkl') processed_ddf.to_csv('processed_data_*.csv', index=False) st.success("✅ 数据预处理完成!") # 显示处理后的数据统计 st.subheader("数据质量检查") with st.spinner("计算缺失值统计..."): try: null_counts = processed_ddf.isnull().sum().compute() st.write("缺失值统计:") st.dataframe(null_counts[null_counts > 0]) except: st.warning("缺失值计算失败") # 可视化关键特征分布 st.subheader("关键特征分布") try: sample_ddf = processed_ddf.sample(frac=0.1) sample_df = sample_ddf.compute() # 选择存在的列进行可视化 plot_cols = [] if 'AGE' in sample_df.columns: plot_cols.append('AGE') if 'ONLINE_DAY' in sample_df.columns: plot_cols.append('ONLINE_DAY') if 'PROM_AMT' in sample_df.columns: plot_cols.append('PROM_AMT') if 'DWN_VOL' in sample_df.columns: plot_cols.append('DWN_VOL') if len(plot_cols) >= 4: fig, axes = plt.subplots(2, 2, figsize=(12, 10)) for i, col in enumerate(plot_cols[:4]): sns.histplot(sample_df[col], ax=axes[i//2, i%2], kde=True) plt.tight_layout() st.pyplot(fig) else: st.warning("缺少足够的列进行可视化") except: st.error("关键特征分布可视化失败") # 目标变量分布 st.subheader("目标变量分布") if 'is_rh_next' in sample_df.columns: fig, ax = plt.subplots(figsize=(6, 4)) sns.countplot(x='is_rh_next', data=sample_df, ax=ax) ax.set_xlabel("是否转化 (0=未转化, 1=转化)") ax.set_ylabel("用户数量") ax.set_title("用户转化分布") st.pyplot(fig) else: st.warning("未找到目标变量 'is_rh_next'") # 特征与目标变量相关性 st.subheader("特征与转化的相关性") if 'is_rh_next' in sample_df.columns: with st.spinner("计算特征相关性..."): try: # 使用采样数据计算相关性 correlation = sample_df[feature_cols + ['is_rh_next']].corr()['is_rh_next'].sort_values(ascending=False) fig, ax = plt.subplots(figsize=(10, 6)) sns.barplot(x=correlation.values, y=correlation.index, ax=ax) ax.set_title("特征与转化的相关性") st.pyplot(fig) except: st.error("特征相关性计算失败") else: st.warning("未找到目标变量 'is_rh_next'") # 模型训练按钮 if st.button("开始模型训练", use_container_width=True): # 检查预处理文件是否存在 if not any(fname.startswith('processed_data_') for fname in os.listdir('.')): st.error("请先进行数据预处理") else: # 创建Spark会话 spark = create_spark_session() # 使用通配符读取所有预处理文件 spark_df = spark.read.csv('processed_data_*.csv', header=True, inferSchema=True) # 加载预处理参数 preprocessor_params = joblib.load('preprocessor_params.pkl') feature_cols = preprocessor_params['feature_cols'] # 训练模型 with st.spinner("正在训练模型,请耐心等待..."): results = train_models(spark_df, feature_cols) # 保存模型结果 joblib.dump(results, 'model_results.pkl') st.success("🎉 模型训练完成!") # 显示模型比较 st.subheader("模型性能对比") model_performance = pd.DataFrame({ '模型': ['逻辑回归', '决策树', '随机森林'], '准确率': [results['逻辑回归']['accuracy'], results['决策树']['accuracy'], results['随机森林']['accuracy']], 'AUC': [results['逻辑回归']['auc'], results['决策树']['auc'], results['随机森林']['auc']] }).sort_values('AUC', ascending=False) st.table(model_performance.style.format({ '准确率': '{:.2%}', 'AUC': '{:.4f}' })) # 最佳模型特征重要性 best_model_name = model_performance.iloc[0]['模型'] model_map = { '逻辑回归': 'logistic_regression', '决策树': 'decision_tree', '随机森林': 'random_forest' } best_model_key = model_map[best_model_name] best_model = results[best_model_key]['model'].bestModel st.subheader(f"最佳模型 ({best_model_name}) 分析") if best_model_key in ['decision_tree', 'random_forest']: feature_importances = results[best_model_key]['feature_importances'] importance_df = pd.DataFrame({ '特征': feature_cols, '重要性': feature_importances }).sort_values('重要性', ascending=False).head(10) fig, ax = plt.subplots(figsize=(10, 6)) sns.barplot(x='重要性', y='特征', data=importance_df, ax=ax) ax.set_title('Top 10 重要特征') st.pyplot(fig) # 显示最佳模型参数 st.subheader("最佳模型参数") params = results[best_model_key]['best_params'] param_table = pd.DataFrame({ '参数': [str(param.name) for param in params.keys()], '值': [str(value) for value in params.values()] }) st.table(param_table) except Exception as e: st.error(f"数据处理错误: {str(e)}") st.exception(e) else: st.markdown("模型分析查看已有模型的详细分析结果", unsafe_allow_html=True) if not os.path.exists('model_results.pkl'): st.info("ℹ️ 当前没有可用模型。请先进行模型训练以生成分析报告。") else: results = joblib.load('model_results.pkl') preprocessor_params = joblib.load('preprocessor_params.pkl') feature_cols = preprocessor_params['feature_cols'] model_choice = st.selectbox( "选择要分析的模型", ("逻辑回归", "决策树", "随机森林") ) model_key = model_choice.lower().replace(" ", "_") # 显示模型基本信息 model_info = results[model_choice] st.markdown(f""" {model_choice} AUC得分: {model_info['auc']:.4f} 准确率: {model_info['accuracy']:.2%} """, unsafe_allow_html=True) # 显示参数详情 with st.expander("模型参数详情", expanded=False): params = model_info['best_params'] param_table = pd.DataFrame({ '参数': [str(param.name) for param in params.keys()], '值': [str(value) for value in params.values()] }) st.table(param_table) # 特征重要性分析 if model_key in ['decision_tree', 'random_forest']: feature_importances = model_info['feature_importances'] importance_df = pd.DataFrame({ '特征': feature_cols, '重要性': feature_importances }).sort_values('重要性', ascending=False) st.subheader("特征重要性分析") top_features = importance_df.head(10) fig, ax = plt.subplots(figsize=(10, 6)) sns.barplot(x='重要性', y='特征', data=top_features, ax=ax) ax.set_title('Top 10 重要特征') st.pyplot(fig) fig, ax = plt.subplots(figsize=(10, 6)) sns.histplot(importance_df['重要性'], bins=20, ax=ax) ax.set_title('特征重要性分布') st.pyplot(fig) st.write("特征重要性详细数据:") st.dataframe(importance_df.style.background_gradient(subset=['重要性'], cmap='viridis')) # 模型比较 st.subheader("与其他模型的对比") model_performance = pd.DataFrame({ '模型': ['逻辑回归', '决策树', '随机森林'], '准确率': [results['逻辑回归']['accuracy'], results['决策树']['accuracy'], results['随机森林']['accuracy']], 'AUC': [results['逻辑回归']['auc'], results['决策树']['auc'], results['随机森林']['auc']] }).sort_values('AUC', ascending=False) fig, ax = plt.subplots(figsize=(10, 6)) model_performance.set_index('模型')[['AUC', '准确率']].plot(kind='bar', ax=ax) ax.set_title('模型性能对比') ax.set_ylabel('评分') plt.xticks(rotation=0) st.pyplot(fig) st.table(model_performance.style.format({ '准确率': '{:.2%}', 'AUC': '{:.4f}' }).apply(lambda x: ['background: lightgreen' if x.name == model_performance.index[0] else '' for _ in x])) # 页脚 st.markdown("—") st.markdown(""" © 2023 单宽转融用户预测系统 | 2231030273 基于Streamlit和Spark开发 """, unsafe_allow_html=True) 执行上述代码出现如下报错,给出修改后完整代码 数据处理错误: could not convert string to float: 'a1' ValueError: could not convert string to float: 'a1' Traceback: File "D:\2035946879\Single_breadth_to_melt.py", line 428, in <module> processed_ddf, feature_cols, means, numeric_cols_for_scaling, scaler = preprocess_data(raw_ddf) ^^^^^^^^^^^^^^^^^^^^^^^^ File "D:\2035946879\Single_breadth_to_melt.py", line 179, in preprocess_data means = processed_ddf[numeric_cols].mean().compute() ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "D:\Anaconda\Lib\site-packages\dask\base.py", line 373, in compute (result,) = compute(self, traverse=False, **kwargs) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "D:\Anaconda\Lib\site-packages\dask\base.py", line 681, in compute results = schedule(expr, keys, **kwargs) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "D:\Anaconda\Lib\site-packages\pandas\core\arrays\arrow\array.py", line 1401, in to_numpy result = result.astype(dtype, copy=False) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

已知,项目的根目录下有一个文件夹"svm_train_images",里面可以保存多张扩展名可能为'.jpg', '.jpeg', '.png', '.bmp', '.webp'的图片,同时还存在一些非以上扩展名的配置文件。现在需要你帮我用Python编写一个Flask应用,进行图片和标签的管理。该应用需要实现以下功能或满足以下需求:1. 查看"svm_train_images"目录下的图片(和图片对应的标签),和图片的删除功能(图片比较多,你需要考虑分页)。2. 可以上传新的图片到"svm_train_images"文件夹内。3. 查看"svm_train_images"目录下的图片时,需要按照上传时间,以最后上传的放在首要位置查看,换而言之你需要以上传时间逆序排列图片。4. 上传新的图片时,需要上传者指定该图片是“正向样例”或者“反向样例”形成图片的标签。5. 图片的标签保存在"svm_train_images/labels.json",此文件内格式示例如下,图片新增和删除的时候需要更新此标签文件(文件名部分你可以根据你的需要调整): json { "4654f617a0b7665408df49ef17e414c1.jpg": "√", "269d6048fad09b3def3f8f58cec2bc46.jpg": "√", "1486c224c9926cdd5dafb43ff0749414.jpg": "×" } 其中,符号"√"表示正向样例,符号"×"表示反向样例。你将应用的完整代码给我,如果一个文件不能给完整需要拆分多个文件,你需要告诉我每个文件的文件名、路径以及文件内的代码。你编写的是Demo,可以不需要权限校验之类的额外功能。

最新推荐

recommend-type

jquery的使用完成自动搜索功能

例如,`$(this)`代表当前元素,`$("p")`选择所有段落,`$(".intro")`选择所有class为"intro"的元素,`$("#info")`选择ID为"info"的元素,`$("ul li.first")`选择每个`&lt;ul&gt;`的第一个`&lt;li&gt;`元素,`$("[href$='.jpg']")...
recommend-type

Fortune Teller (霄占)_ A Python-based divination platform that

Fortune Teller (霄占)_ A Python-based divination platform that uses LLMs to provide personalized fortune readings through multiple systems including BaZi, tarot, and zodiac astrology. 基于Python的多系统算命程序,使用LLM(大型语言模型)进行解读。.zip
recommend-type

一个基于qwen-max-latest(LLM) + paraformer-realtime-v2(ASR)的一个实时语

一个基于qwen-max-latest(LLM) + paraformer-realtime-v2(ASR)的一个实时语音AI面试助手.zip
recommend-type

基于LLM开发的一些应用工具.zip

基于LLM开发的一些应用工具.zip
recommend-type

一个基于LLM的高质量数据生成器.zip

一个基于LLM的高质量数据生成器.zip
recommend-type

破解dex2jar: Android应用反编译与分析指南

标题中的“dex2jar”指的是一个用于将Android应用程序中的DEX文件(Dalvik可执行文件)转换成Java JAR文件的工具。这个过程被称为“DEX转JAR”,是一个逆向工程的过程,它允许开发者查看和分析Android应用程序的原始Java代码,这通常用于学习、测试和安全分析目的。破解一词在此上下文中可能用于描述不正当手段获取程序的源代码以进行修改或绕过安全机制等行为,但请注意,任何未经授权的修改和使用都可能违反法律和版权。 描述部分提供了使用dex2jar工具的基本步骤。dex2jar通常是一个批处理文件(dex2jar.bat),用于在Windows环境下执行操作。它将DEX文件(classes.dex)作为输入,并生成对应的JAR文件。这个过程需要用户已经下载并解压了dex2jar的压缩包,并将其中的dex2jar.bat文件放在一个可以访问的目录中。然后,用户需要将目标Android应用程序中的classes.dex文件复制到该目录下,并在命令行界面中运行以下命令: dex2jar.bat classes.dex 执行完毕后,会在同一目录下生成名为classes.dex.dex2jar.jar的文件。这个JAR文件实质上是将DEX文件中的类转换成了Java的.class文件,并打包成了JAR格式,供后续的分析或修改使用。 【标签】中的“Android 破解”可能被误解为破解Android应用程序的安全机制,实际上,这个标签更准确的意义是分析和理解Android应用程序的工作原理。而“jar dex”则是指JAR文件与DEX文件之间的转换关系。 【压缩包子文件的文件名称列表】中列举了几个文件名,其中“使用说明.txt”很可能是该工具的官方使用指南,提供更详细的使用说明、安装步骤和常见问题的解答。“dex2jar最新版下载dex2jar下载 2.0官方版_ - pc6下载站.url”则是一个指向PC6下载站上dex2jar工具下载页面的网页链接。“dex2jar-2.0”可能是指特定版本号的dex2jar工具压缩包。 总结以上信息,dex2jar是一个专门用于Android应用安全分析和逆向工程的工具,它将DEX文件转换成Java的JAR文件以便进行代码的查看和分析。尽管这个过程在安全研究和教育目的下是合法且有价值的,但任何使用这一工具的非法破解、侵犯版权或进行恶意行为都是不可接受的。
recommend-type

共享内存与共识算法详解

### 共享内存与共识算法详解 在分布式系统中,共享内存和共识算法是两个核心的概念。共享内存涉及到多个进程对同一数据的读写操作,而共识算法则帮助多个进程在可能存在故障的情况下达成一致的决策。下面将详细介绍这两个方面的相关知识。 #### 共享内存 共享内存的寄存器规范由Lamport在多篇论文中引入,同时定义了安全、规则和原子三种语义。 - **安全寄存器**:在三种寄存器语义中,安全寄存器提供的保证最弱。当读操作与写操作并发时,安全寄存器可能返回任意值,甚至可能是从未被写入或尝试写入的值。在多处理器机器中,进程可能并发访问实现寄存器抽象的底层硬件位置,从而导致返回任意值。在任意故障模型中
recommend-type

计算机专业本科生和研究生就业薪资待遇

<think>好的,我需要回答用户关于计算机专业本科和研究生就业薪资对比的问题。首先,我得先看看用户提供的引用资料,看看里面有没有相关的数据。 引用[4]提到,2019届计算机类本科毕业生的平均月收入是6858元,而高职是4883元。这应该可以作为本科生的参考数据。至于研究生,引用[1]指出重庆大学的计算机和软件硕士就业情况良好,薪资高于行业平均水平,但没有具体数字。不过引用[3]提到,前20名的高校多为985/211,尤其是理工类院校的毕业生薪资更高。这里可能需要结合其他信息来推断研究生的薪资水平。 另外,引用[2]提到计算机专业毕业生薪资一般在万元以上,但不确定这是否特指研究生还是包括
recommend-type

eWebEditor 10.3最新版特性与安全升级指南

从提供的信息来看,我们需要深入了解和探讨的内容主要集中在“eWebEditor最新版”这一主题上。eWebEditor是一款流行的在线HTML编辑器,它支持ASP和ASP.NET环境,并广泛用于Web内容管理。通过给出的标题和描述,以及标签和文件名称列表,我们可以推导出一系列相关的知识点。 ### 标题知识点解析 #### eWebEditor的定义与功能 “eWebEditor最新版”中提到的“eWebEditor”指的是在线HTML编辑器产品,它被广泛应用于需要在线编辑和发布网页内容的场合。编辑器通常包含许多功能,比如文本格式化、图像插入、链接管理等,提供用户友好和接近桌面程序的编辑体验。eWebEditor产品以ASP和ASP.NET作为其主要的技术平台。 #### “最新版”更新内容 “最新版”表明我们正在讨论的是eWebEditor的最新版本更新,该版本很可能是为了增加新功能、提升性能、修复已知问题或改善安全性能。一般来说,软件的更新也可能会引入对新操作系统或浏览器的兼容性,以及对现有API或开发环境的新支持。 ### 描述知识点解析 #### “亲测可用”的含义 从“亲测 可用”的描述中我们可以推断出,发布者可能已经对“eWebEditor最新版”进行了测试,并验证了其在实际使用中的性能和稳定性。该短语传递出一个积极的信号,即该版本值得信赖,用户可以期待它将正常工作,无需担心兼容性或功能缺失的问题。 ### 标签知识点解析 #### eWebEditor的版本标识 “eWebEditor ASPX 10.3 最新版”中的标签指出我们讨论的版本号为10.3,这是一个具体的产品版本,意味着它可能包含了一些特定的更新或新增特性。通过版本号,我们可以推断产品已经经过了多次迭代和改进。 #### ASPX技术框架 在标签中提到的“ASPX”,这表明eWebEditor最新版支持ASP.NET Web Forms技术,ASPX是ASP.NET网页的标准文件扩展名。这一信息指出编辑器适合使用.NET框架的网站开发环境。 ### 文件名称列表知识点解析 #### “升级说明.txt”文件 “升级说明.txt”是一个文本文件,它可能包含了eWebEditor从上一版本升级到最新版本时的变化说明,例如新增功能、改进的地方以及需要注意的变更。开发者或维护人员在升级时应该仔细阅读这些说明,以便于平滑过渡到新版本,并最大化地利用新功能。 #### “安全说明.txt”文件 “安全说明.txt”文件通常提供了关于软件安全性的相关信息,这可能包括了针对最新版的安全补丁、修复的安全漏洞列表以及安全最佳实践的建议。特别是对于在线编辑器这类直接参与网页内容生成的工具,安全尤为重要,因此,安全说明文件对于确保编辑器和整个网站的安全运行至关重要。 #### “ewebeditor”文件夹或组件 “ewebeditor”可能是实际包含eWebEditor编辑器文件的文件夹名称。通常,这类文件夹内会包含用于前端的JavaScript文件、用于后端处理的服务器端代码(ASP.NET或ASP代码),以及相关的样式文件和资源文件。对于开发者来说,了解这些文件和组件的组织结构对于集成和配置编辑器至关重要。 综合以上信息,我们可以了解到eWebEditor的最新版本更新了很多内容,可能包含性能和安全性的提升,并可能对特定的技术平台如ASP.NET提供了更好的支持。用户应该参考升级和安全说明文件,以便正确理解和应用这些更新。对于开发者而言,掌握如何在项目中部署和配置eWebEditor编辑器也是一个重要的技能点。
recommend-type

分布式系统中的时间抽象与故障处理

### 分布式系统中的时间抽象与故障处理 #### 1. 故障检测概述 在分布式系统中,存在三种不同的系统假设:异步系统假设、同步系统假设和部分同步系统假设。异步系统不包含任何时间假设,我们的进程和链路抽象直接体现了这一点。然而,这些抽象不足以定义同步和部分同步系统。 为了添加时间假设,一种方法是用时间保证来扩展进程和链路抽象,但这会导致规范过于复杂。因此,我们引入了故障检测器的抽象概念,它能提供关于哪些进程崩溃、哪些进程正常的信息,不过这些信息不一定准确。 故障检测器抽象相较于直接对进程和链路做时间假设具有以下两个优势: - 减轻了用时间假设扩展进程和链路抽象的需求,保留了这些抽象的简