Spring Boot整合WebMagic与MyBatis多数据源:爬虫工程化落地指南
简介面向Java开发者的SpringBootWebMagicMybatis多数据源爬虫框架资源适合已掌握基础爬虫或Spring Boot、希望构建企业级可扩展采集系统的学习者。资源共134个文件涵盖java源码、class编译文件、mybatis映射xml、properties与yml配置等压缩包约64.89MB目录按功能模块划分便于快速定位与复盘。项目完整演示了WebMagic核心组件Downloader、PageModel、Pipeline、Scheduler与SpringBoot的整合方式同时通过BaseDataSourceConfig、SecondDbDataSourceConfig等配置类实现多数据源切换并利用AOP管理事务保证数据一致性Mybatis动态SQL则简化了持久层开发。源码包含小说、漫画、截图等多类站点爬虫实例及ElasticSearch工具类可直接参考其解析逻辑与存储策略。已有1280人学习下载能够帮助读者掌握从页面解析、数据持久化到多库切换的完整爬虫工程化流程。1. 把爬虫做成 Spring Boot WebMagic MyBatis 多数据源的项目到底解决了什么你手头那个爬虫大概率还停在一个public static void main里启动靠 IDE 点运行抓取循环靠while(true)硬撑数据落库靠 JDBC 手写挂了没人知道跑了几天数据写乱了也没人知道。把爬虫工程化成“基于 Spring Boot WebMagic MyBatis 多数据源”的一套项目不是我技术过剩而是爬虫这东西只要过了单机演示阶段就必然碰到三个问题生命周期没人管、持久化没人管、业务数据与抓取中间数据混在一起没人管。这篇文章就把这套技术栈为什么这么配、怎么落地、参数怎么设、坑在哪一次说清楚。适合已经写过简单爬虫、想把采集能力做成正经服务的 Java 开发者。2. 工程骨架先弄清楚 WebMagic 的四个组件再谈 Spring Boot 整合2.1 WebMagic 的核心抽象Downloader、PageProcessor、Scheduler、Pipeline 的职责边界WebMagic 是一个轻量级 Java 爬虫框架它的核心抽象其实就四个接口理解这四个东西的边界比记 API 重要得多。Downloader 负责把 URL 变成 HTML 字节流默认用 HttpClient你基本不用动它。PageProcessor 是唯一需要你认真实现的组件职责是“给定一个页面提取哪些 URL 继续抓、提取哪些字段作为结果”。Scheduler 管 URL 队列和去重默认的 QueueScheduler 在内存里维护一个 Hash 去重集合只管单机不重启场景。Pipeline 是结果出口PageProcessor 产出结构化字段后由 Pipeline 消费并写入 MySQL、ES 或者文件。这套抽象的价值在于抓取、解析、调度、持久化四个环节完全解耦。你想换存储只改 Pipeline想换抓取策略只改 PageProcessor想让多个爬虫任务共享 URL 调度可以换 RedisScheduler。Spring Boot 在这里的角色不是重写 WebMagic而是接管这些组件的生命周期、依赖注入和运行管理让爬虫从“一次性的 main 方法”变成“常驻的服务”。2.2 依赖选型pom 里带哪些包版本陷阱在哪一个最小可跑的工程依赖控制在五个左右就够了。Spring Boot 用 2.7.x 系列稳定且和 MyBatis Starter 兼容好。WebMagic 的坐标是us.codecraft注意它没有进 Spring Initializr需要自己去 Maven 中央仓库拉。核心包和扩展包建议一起引入扩展包里有注解模式、Redis 调度器这些你可能用得上的工具。dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.mybatis.spring.boot/groupId artifactIdmybatis-spring-boot-starter/artifactId version2.3.1/version /dependency dependency groupIdus.codecraft/groupId artifactIdwebmagic-core/artifactId version0.7.3/version /dependency dependency groupIdus.codecraft/groupId artifactIdwebmagic-extension/artifactId version0.7.3/version /dependencywebmagic 0.7.3 有一个隐性问题它传递依赖了老版本的 httpclient和 Spring Boot 自带的 httpclient 版本不一致时可能出现NoSuchMethodError。我这个项目里的做法是显式声明一个较新的 httpclient 版本覆盖它版本号用 4.5.14兼容性和稳定性都验证过。如果你用的是 Spring Boot 3.x 和 jakarta 命名空间建议先降回 2.7.x因为 webmagic 的代码基于 javax 编写迁移成本不值得在初期承担。2.3 包结构把爬虫业务和基础设施从第一天就分开包结构直接决定这个项目三个月后谁还敢改。我惯用的分包方式是按“基础设施”和“业务采集”两条线走。基础设施只放数据源、切面、配置类这些通用逻辑业务采集层放每个站点的 PageProcessor、Pipeline 和对应的 Mapper。这样做的好处是你接第二个采集目标时只需要在 crawler 下新增一个包不需要改动任何基础设施代码。├── config/ # 数据源、MyBatis、爬虫线程池配置 ├── datasource/ # 动态数据源路由、ThreadLocal 管理 ├── aop/ # 数据源切换切面 ├── crawler/ │ └── article/ # 某个站点抓取规则 │ ├── ArticlePageProcessor.java │ └── ArticlePipeline.java ├── mapper/ # 按业务域拆分的 Mapper 接口与 XML ├── service/ # 落库、清洗、任务调度逻辑 ├── entity/ # 领域模型 └── SpiderApplication.java实体类和 Mapper 不要放在 crawler 包里面。PageProcessor 是抓取规则Mapper 是持久化规则它们变更的节奏完全不一样站点改版你会改 PageProcessor表结构变更你会改 Mapper混在一起以后 git 历史会非常难看。2.4 把 Spider 交还给 Spring 管理启动、关闭和 Bean 生命周期新手最容易翻车的地方是在 CommandLineRunner 里new Spider()然后直接跑。这么做的后果是PageProcessor 和 Pipeline 都由你手动 new 出来Spring 容器完全不知道它们的存在Autowired注入的 Mapper 或 Service 全是 null。正确做法是把 PageProcessor、Pipeline 和 SpiderRunner 全部交给 Spring 管理。Component public class SpiderRunner implements CommandLineRunner { Autowired private ArticlePageProcessor pageProcessor; Autowired private ArticlePipeline pipeline; Override public void run(String... args) { Spider.create(pageProcessor) .addUrl(https://example.com/article/list/1) .thread(4) .addPipeline(pipeline) .runAsync(); } }用runAsync()而不是run()是因为run()会阻塞当前线程而 Spring Boot 的 CommandLineRunner 在 web 场景下阻塞会导致一些你不想遇到的启动时序问题。runAsync()启动后配合 JVM 的 ShutdownHook在应用关闭时调用spider.close()释放下载线程和连接池避免端口占用和连接泄漏。这点在你用kill -15优雅停机时尤其重要。3. 解析层设计用 PageProcessor 把页面结构和业务数据解耦3.1 先定义领域模型再写选择器写 PageProcessor 前先把结果模型定下来。很多教程直接往ResultItems里塞 XPath 字符串结果临时跑没问题但一旦字段多了Pipeline 里拿到的是一堆无类型的字符串清洗和落库都很难受。我会先写一个干净的实体类字段类型明确再考虑页面里的选择器怎么映射。public class Article { private Long id; private String title; private String content; private String author; private Date publishTime; private String sourceUrl; // getter / setter 省略 }注意一点sourceUrl必须保留原始链接。不要觉得它占空间就省掉它是后续增量抓取、去重、异常排查的唯一依据。没有源 URL数据出了问题你连回查都做不到。3.2 三种解析方式的选型XPath、CSS 选择器与正则WebMagic 的 Selector 体系提供了 XPath、CSS 和正则三种解析方式选型原则很简单结构清晰的页面用 XPath带 class 和 id 的页面用 CSSXPath 抓不到的边界情况用正则兜底。下面是它们各自适合的场景参照。解析方式适合场景劣势典型写法XPath文档结构稳定的页面属性提取方便依赖 DOM 层级页面改版易碎//h1[classtitle]/text()CSS 选择器页面用 class/id 标记内容只能选节点属性提取稍弱div.content h1正则从 HTML 属性或 JSON 片段里提取难维护优先用作兜底articleId:(\d)实际操作中我 80% 的场景用 XPath因为 WebMagic 的 XPath 扩展支持//div[classcontent]/html()这种直接拿内部 HTML 的写法比 CSS 选择器拼接省事得多。不要把三种选择器混用在同一个字段上代码会变得没法读。3.3 列表页到详情页URL 管理是调度器的活抓取一个资讯站的常规路径是入口 URL 是列表页列表页里包含详情页链接和下一页链接。PageProcessor 里用一个正则区分的策略让同一个 Processor 同时处理两种页面。public class ArticlePageProcessor implements PageProcessor { private static final String ARTICLE_URL https://example\\.com/article/\\d; private static final String LIST_URL https://example\\.com/article/list/\\d; private Site site Site.me() .setRetryTimes(3) .setSleepTime(300) .setCharset(UTF-8) .setUserAgent(Mozilla/5.0 (Windows NT 10.0; Win64; x64)); Override public void process(Page page) { if (page.getUrl().regex(ARTICLE_URL).match()) { page.putField(title, page.getHtml().xpath(//h1[classarticle-title]/text())); page.putField(content, page.getHtml().xpath(//div[classarticle-content]/html())); page.putField(publishTime, page.getHtml().xpath(//span[classdate]/text())); } else if (page.getUrl().regex(LIST_URL).match()) { page.addTargetRequests(page.getHtml().links().regex(ARTICLE_URL).all()); page.addTargetRequests(page.getHtml().links().regex(LIST_URL).all()); } } Override public Site getSite() { return site; } }这段代码的关键是addTargetRequests与putField的分工。详情页命中后只产出字段不放 URL 继续抓避免进入死循环列表页只负责发现新链接不产生业务数据。regex匹配用的是links()方法它会自动从a href中抽取绝对路径并做补全比手写正则提取 href 再拼域名省事很多也少踩绝对路径和相对路径混用的坑。3.4 Pipeline 做出口抓取结果如何交给持久化层putField的结果会通过ResultItems传给 PipelinePipeline 的职责只有两个判断结果是否为空、把字段映射成实体类后走 Service 落库。不要在 Pipeline 里写 XPath 选择器也不要在 PageProcessor 里写任何数据库相关代码。Component public class ArticlePipeline implements Pipeline { Autowired private ArticleService articleService; Override public void process(ResultItems resultItems, Task task) { MapString, Object fields resultItems.getAll(); if (fields.isEmpty()) { return; } String title (String) fields.get(title); if (StringUtils.isBlank(title)) { return; } Article article new Article(); BeanUtils.populate(article, fields); article.setSourceUrl(resultItems.getRequest().getUrl()); articleService.insert(article); } }这里有个容易被忽略的细节resultItems.getRequest().getUrl()拿到的才是真实抓取页面 URLputField塞进去的字段不带这个信息需要单独取。为空判断也不可少列表页虽然不 put 字段但ResultItems的getAll()不会因为你是列表页就返回 null——它在每个页面都会触发 Pipeline。这个空判断挡住的是页面结构变更时产生的垃圾数据。4. 多数据源落地为什么爬虫项目需要两个数据源以上以及动态路由怎么做4.1 爬虫项目多数据源的三个典型场景很多人觉得多数据源是过度设计但爬虫这个领域恰恰相反。第一个典型场景是原始快照和结构化数据分离抓下来的页面原文可能包含异常 HTML、脚本片段直接混合进业务表会让查询和索引都变得很慢单独放一个快照库更合适。第二个场景是 URL 去重库与内容库分离去重库的读写频率远高于内容库高频的 select 和 insert 如果共用连接池容易出现连接等待。第三个场景是写主库、读从库拆分爬虫任务写库压力大但业务侧要实时查询主从分离后抓取任务不阻塞查询。用 Spring 自带的AbstractRoutingDataSource做动态路由是这几个场景最省事的解法。它本质上是一个代理数据源维护一个 key 到真实数据源的映射表每次getConnection()的时候根据当前线程的 key 决定返回哪个真实数据源的连接。这个设计的精妙之处在于MyBatis、事务管理器甚至连接池都只感知到这一个数据源路由逻辑对上层完全透明。4.2 AbstractRoutingDataSource ThreadLocal动态路由的核心代码实现动态路由只需要三个类。第一个是 ThreadLocal 管理类负责保存当前线程要使用的数据源 key。第二个是路由数据源继承AbstractRoutingDataSource重写determineCurrentLookupKey()。第三个是配置类把两个真实数据源放进路由表。public class DynamicDataSourceHolder { private static final ThreadLocalString KEY new ThreadLocal(); public static void setKey(String key) { KEY.set(key); } public static String getKey() { return KEY.get(); } public static void clear() { KEY.remove(); } }public class DynamicDataSource extends AbstractRoutingDataSource { Override protected Object determineCurrentLookupKey() { return DynamicDataSourceHolder.getKey(); } }Configuration public class DataSourceConfig { Bean(name masterDataSource) ConfigurationProperties(prefix spring.datasource.master) public DataSource masterDataSource() { return DataSourceBuilder.create().build(); } Bean(name slaveDataSource) ConfigurationProperties(prefix spring.datasource.slave) public DataSource slaveDataSource() { return DataSourceBuilder.create().build(); } Bean public DynamicDataSource dynamicDataSource( Qualifier(masterDataSource) DataSource master, Qualifier(slaveDataSource) DataSource slave) { DynamicDataSource dynamicDataSource new DynamicDataSource(); MapObject, Object dataSources new HashMap(4); dataSources.put(DataSourceKey.MASTER, master); dataSources.put(DataSourceKey.SLAVE, slave); dynamicDataSource.setTargetDataSources(dataSources); dynamicDataSource.setDefaultTargetDataSource(master); return dynamicDataSource; } }三点值得展开说。第一setDefaultTargetDataSource是保底用的如果 ThreadLocal 里没有 key 或者 key 不存在就落默认数据源避免配置遗漏直接报错。第二AbstractRoutingDataSource在创建连接时会调用determineCurrentLookupKey()所以 ThreadLocal 中的 key 必须在getConnection()之前设置。第三DataSourceBuilder.create().build()会读取spring.datasource.master.*前缀下的 url、username、password 和连接池参数这些配置写在application.yml里。4.3 注解式路由用 AOP 控制数据源切换而不是手动 set直接在业务代码里DynamicDataSourceHolder.setKey()再try/finally clear()也能跑但每个方法都写一遍就太啰嗦了。更好的方式是用一个注解加一个切面把路由逻辑集中在一处。先定义一个注解然后在 Mapper 方法或 Service 方法上标注目标数据源。Target({ElementType.METHOD, ElementType.TYPE}) Retention(RetentionPolicy.RUNTIME) public interface TargetDataSource { String value() default DataSourceKey.MASTER; }Aspect Component public class DataSourceAspect { private static final Logger log LoggerFactory.getLogger(DataSourceAspect.class); Before(annotation(targetDataSource)) public void switchDataSource(JoinPoint point, TargetDataSource targetDataSource) { DynamicDataSourceHolder.setKey(targetDataSource.value()); log.info(route dataSource - {}, method: {}, targetDataSource.value(), point.getSignature().toShortString()); } After(annotation(targetDataSource)) public void clearDataSource(TargetDataSource targetDataSource) { DynamicDataSourceHolder.clear(); } }这里有一个非常关键的顺序问题切面的Before必须在事务开启之前执行否则事务管理器已经通过默认数据源拿到了连接后面再切 key 也晚了。Spring 默认情况下 AOP 切面的执行顺序在事务切面之前所以常规写法没问题。但如果你给切面加了Order调整了执行顺序就有可能出现路由失效。这就是为什么我要求把TargetDataSource放在 Service 层方法上而不是 Mapper 方法上——Service 层是事务边界数据和路由在同一个边界生效日志也容易定位。4.4 MyBatis 多数据源的 Mapper 归置包必须彻底拆开动态数据源解决了连接路由问题但 MyBatis 的 Mapper 扫描又带来一个新的组织问题。我的建议是两个数据源对应两个 Mapper 包分别用MapperScan指定绝不互相包含。Configuration MapperScan(basePackages com.example.spider.mapper.master, sqlSessionFactoryRef masterSqlSessionFactory) public class MasterMybatisConfig { Bean public SqlSessionFactory masterSqlSessionFactory( Qualifier(dynamicDataSource) DataSource dataSource) throws Exception { SqlSessionFactoryBean factoryBean new SqlSessionFactoryBean(); factoryBean.setDataSource(dataSource); factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver() .getResources(classpath*:mapper/master/*.xml)); return factoryBean.getObject(); } }从库的配置把包名和 XML 路径换成 slave 的对应值即可。注意这里两个 SqlSessionFactory 用的都是同一个 dynamicDataSource而不是各自绑定独立 DataSource——这样 Mapper 方法才能通过动态路由切换数据源。真正的隔离靠的是 Mapper 接口所在的包而不是数据源本身的拆分。如果你不小心把两个包的 XML 都放到同一个目录下最终结果是某个 SqlSessionFactory 找不到 statement启动直接报错。5. 避坑实录WebMagic MyBatis 多数据源最容易踩的 5 个翻车现场5.1 PageProcessor 里注入的 Mapper 是 null一跑就 NPE现象PageProcessor 里Autowired了一个统计接口爬虫启动后第一行日志就报空指针。 原因你在Spider.create(pageProcessor)之前用new ArticlePageProcessor()创建了对象Spring 的依赖注入根本管不到这个手动 new 的实例。凡是脱离容器管理的组件Autowired全部无效。 解决把 PageProcessor 和 Pipeline 都注册成 Spring Bean在 CommandLineRunner 里通过构造注入或直接Autowired获取。如果 PageProcessor 内部有状态比如统计计数把它改成无状态 Bean统计逻辑放到独立的 Service 里避免多线程共享可变量。5.2 MapperScan 包写太宽两个 SqlSessionFactory 启动就打架现象启动时抛org.apache.ibatis.binding.BindingException: Invalid bound statement (not found)或者提示某个 Mapper 重复注册。 原因两个MapperScan的basePackages都扫到了同一个包Master 和 Slave 的 SqlSessionFactory 都尝试为同一个接口创建代理产生冲突。XML 文件的位置如果不按 SqlSessionFactory 的mapperLocations精确隔离也会出现 statement 匹配错乱。 解决包结构按数据源彻底拆分Master 只扫mapper.master.*Slave 只扫mapper.slave.*XML 分别放在resources/mapper/master/和resources/mapper/slave/下。这条规则在项目第一天就定下来别等到 Mapper 多了才整理。5.3 事务方法内部切换数据源路由死活不生效现象Service 方法标了Transactional里面又调用了标了TargetDataSource(SLAVE)的查询方法结果查出来的数据还是主库的。 原因Spring 事务管理器在进入事务方法时会立刻通过数据源拿一个连接并绑定到当前线程的事务同步管理器。后续在这个事务内执行的所有 SQL 都复用同一个连接路由 key 再变也改变不了已经拿到的物理连接。 解决把数据源切换放到事务边界之前或者把需要切换数据源的逻辑从当前事务中拆出去。常见做法是让事务方法内部通过一个不带事务的 Service 代理去执行查询确保getConnection()发生在determineCurrentLookupKey()返回正确 key 之后。// 错误写法key 设置发生在事务内部连接已经绑定 Transactional public void wrongWay() { DynamicDataSourceHolder.setKey(DataSourceKey.SLAVE); slaveMapper.queryRecentArticle(); } // 正确写法先切 key再进入事务方法 public void rightWay() { DynamicDataSourceHolder.setKey(DataSourceKey.SLAVE); queryService.queryRecentArticleInTx(); DynamicDataSourceHolder.clear(); }这条路我踩过不止一次现象和原因都极其典型排查时先看事务边界再看切面执行顺序基本能定位。5.4 跑了几小时后连接池耗尽爬虫假死现象爬虫任务刚开始正常跑了几个小时后日志出现Connection is not available, request timed out after 30000ms抓取速度骤降为 0。 原因HikariCP 默认最大连接池是 10而 WebMagic 的下载线程池默认 4 个线程Pipeline 写库慢加上列表页处理线程也在拿连接10 个连接很快就被占满。更隐蔽的是某个事务里连接没有被及时释放导致连接泄漏。 解决先把连接池调大再考虑异步化。maximum-pool-size我一般设在 30 左右minimum-idle保持 5connection-timeout设 30000 毫秒。同时把 Pipeline 的写库操作改成异步消费抓取结果先放入阻塞队列由单独的写库线程池消费下载线程不再直接等待数据库响应。这么改完连接池占用率会明显下降。5.5 页面中文乱码XPath 提取全错现象标题、正文全部乱码XPath 选择器拿不到任何合法文本。 原因目标站的页面编码不是 UTF-8而是 GBK而且没有在响应头里正确声明。WebMagic 默认编码探测对部分老站点会失败最终用错误编码解码。 解决在Site.me()里显式声明setCharset(GBK)前提是你知道目标站是什么编码。不知道编码时可以在 Downloader 环节拿到原始字节后先探测再设置但不建议在 PageProcessor 里动态设置因为page.getHtml()已经完成了字符解码。最省事的做法是把目标页面的编码确认写成站点配置而不是靠运行时自动探测。6. 进阶习惯让数据源路由跟着任务走不跟线程走多数据源爬虫项目跑稳之后最见功力的一点是路由 key 的生命周期管理。ThreadLocal 在单个线程串行执行时很好用但爬虫几乎一定会用线程池线程池的复用机制会让上一个任务遗留的 key 被下一个任务继承轻则该写主库的写进了从库重则数据直接串库。我吃过大亏才总结出三个习惯。第一个习惯是路由 key 作为任务上下文的一部分在提交任务时显式传入而不是靠 ThreadLocal 隐式传递。具体做法是定义一个CrawlTaskContext里面放 taskId、targetSite、dataSourceKey随任务一起提交。第二个习惯是用TaskDecorator拦截线程池的任务提交在每个任务真正跑起来之前清空 ThreadLocal。Bean(crawlExecutor) public ThreadPoolTaskExecutor crawlExecutor() { ThreadPoolTaskExecutor executor new ThreadPoolTaskExecutor(); executor.setCorePoolSize(4); executor.setMaxPoolSize(8); executor.setQueueCapacity(100); executor.setTaskDecorator(runnable - () - { DynamicDataSourceHolder.clear(); try { runnable.run(); } finally { DynamicDataSourceHolder.clear(); } }); return executor; }这段代码看起来是清空实际上是把路由决策权交还给任务本身。线程池里跑的是上一个任务的残留 key还是当前任务应该设置的 key完全由任务体在进入业务方法前自己决定。第三个习惯是给切面加一行路由日志每次切换都记录“从哪个 key 切到哪个 key、是哪个方法触发的”。排查串库问题时这段日志能在十分钟内定位到是哪个任务、哪条链路、哪个线程干的比半夜翻代码高效得多。坦白说多数据源这个功能在爬虫项目里做对并不难难的是在异步、多线程、事务交叠的情况下始终保持路由正确。我现在的底线是凡是线程池跑的爬虫任务路由 key 一律放进任务上下文不留在 ThreadLocal 里过夜凡是涉及事务的方法数据源切换永远写在事务边界之前凡是不确定路由对不对的线上问题先看切面日志再查代码。这套习惯让我把多数据源爬虫的串库概率降到了零希望帮到你。本文还有配套的精品资源点击获取

相关新闻

Qt开发必备:QJson编译全攻略与CMake集成实战

Qt开发必备:QJson编译全攻略与CMake集成实战

1. 为什么还要折腾 QJson 的编译QJson 这个库,做 Qt 桌面开发的人多少都听过。它是一个基于 Qt 的轻量级 JSON 解析库,能把 JSON 文本直接映射成 QVariant 对象,也能把 QVariant 反向序列化成 JSON 字符串。相比 Qt 自带的 QJsonDocument&…

2026/10/13 23:46:36 阅读更多 →
Python手写线性规划求解器:大M法与两阶段法实现

Python手写线性规划求解器:大M法与两阶段法实现

简介:本资源是一套面向高校运筹学、最优化方法课程学习者及MATLAB初学者的线性规划算法实践工具包,聚焦单纯形法核心变体——大M法与两阶段法的完整程序实现。资源包含3个功能明确的MATLAB源文件(.m):main.m为主控入口…

2026/10/13 23:46:36 阅读更多 →
GNN图神经网络实战:从GCN到GAT、GraphSAGE、GIN的选型与避坑指南

GNN图神经网络实战:从GCN到GAT、GraphSAGE、GIN的选型与避坑指南

简介:这份资源是一套面向深度学习学习者与算法工程师的图神经网络系统课件,共100页PPT,适合希望从零建立GNN知识框架、或需要梳理GCN及其变体脉络的读者。内容按四部分展开:先讲为何需要GNN,对比欧式与非欧式数据的差异…

2026/10/13 23:45:36 阅读更多 →

最新新闻

Tecplot 后处理实战:从数据导入到宏与 Python 批量出图全解析

Tecplot 后处理实战:从数据导入到宏与 Python 批量出图全解析

简介:《Tecplot教程合集》面向流体动力学、热力学与工程仿真领域的工程师、科研人员及高校学生,帮助零基础或有一定经验的用户系统掌握这款科学数据可视化与分析软件。资源以视频教程、真实案例和中文指南三条主线展开,覆盖数据导入与管理、二…

2026/10/14 0:34:52 阅读更多 →
XMLSchema核心概念与工程实践详解

XMLSchema核心概念与工程实践详解

1. XMLSchema 基础概念解析XMLSchema(通常缩写为XSD)是W3C推荐的标准XML结构定义语言。作为DTD的现代替代方案,它使用XML语法本身来描述XML文档的结构约束。我第一次接触XSD是在2008年参与银行系统接口开发时,当时需要确保交易报文…

2026/10/14 0:34:52 阅读更多 →
航空发动机壳体三维设计与加工仿真关键技术

航空发动机壳体三维设计与加工仿真关键技术

1. 项目背景与核心价值飞机减速系统壳体作为航空发动机传动装置的关键承力部件,其结构设计合理性直接影响整机安全性能。这类零件通常采用高强度钛合金或镍基高温合金材料,具有复杂的异形曲面特征和严格的公差要求(通常控制在IT6-IT7级&#…

2026/10/14 0:34:52 阅读更多 →
SpringBoot+Vue企业级贸易CRM系统架构与优化实践

SpringBoot+Vue企业级贸易CRM系统架构与优化实践

1. 企业级贸易CRM系统架构解析作为一名长期从事企业级系统开发的工程师,我深知贸易行业对CRM系统的特殊需求。传统贸易企业面临客户信息分散、订单跟踪困难、数据分析滞后等痛点,这套基于SpringBootVueMyBatis的解决方案正是针对这些痛点设计的完整实现。…

2026/10/14 0:34:52 阅读更多 →
毕业设计选题与实现:从技术路线到项目实战

毕业设计选题与实现:从技术路线到项目实战

1. 毕业设计选题指南:从避坑到创新的完整攻略又到了一年一度的毕业设计季,作为经历过这个阶段的老学长,我深知选题的重要性。一个好的选题不仅能让你顺利通过答辩,更能成为简历上的亮点。最近两年,高校对毕业设计的要求…

2026/10/14 0:34:52 阅读更多 →
tidyr数据整形核心指南:从宽表长表转换到字段拆解

tidyr数据整形核心指南:从宽表长表转换到字段拆解

1. 为什么“整理数据”比“分析数据”更耗时——从一个真实项目说起我带过不少刚接触R语言的某高校数据分析课程学员,也帮某公司做过几轮业务数据清洗支持。几乎所有人第一次拿到原始数据时,第一反应都不是建模或画图,而是盯着Excel里那些“看…

2026/10/14 0:33:52 阅读更多 →

日新闻

AnyPS5通用适配层设计:兼容性与性能的工程取舍

AnyPS5通用适配层设计:兼容性与性能的工程取舍

1. 从"AnyPS5"这个名字说起:它到底想解决什么问题第一次看到"AnyPS5"这个标题,我脑子里冒出来的第一个念头是:这大概率是一个围绕"跨平台运行"或者"通用适配"做文章的项目。名字里的"Any"…

2026/10/14 0:00:41 阅读更多 →
Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

1. 项目概述:Flutter跨平台二手交易应用开发作为一名长期从事移动应用开发的工程师,我最近完成了一个基于Flutter框架的二手物品交易平台项目。这个项目最吸引我的地方在于它完美结合了Flutter的跨平台优势与HarmonyOS的生态特性,实现了"…

2026/10/14 0:00:41 阅读更多 →
DFS算法解决飞机降落调度问题实战

DFS算法解决飞机降落调度问题实战

1. 项目背景与核心思路飞机降落问题是一个经典的调度优化场景,也是算法竞赛和面试中的高频题目。这个题目要求我们合理安排多架飞机的降落顺序和时间,确保所有飞机都能安全着陆而不发生冲突。在实际航空管制中,这类问题直接关系到飞行安全和机…

2026/10/14 0:00:41 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/13 2:54:56 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/13 16:22:04 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/13 1:58:08 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 22:01:27 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 14:57:39 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 18:11:17 阅读更多 →