ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Java批量抓取百度新闻与今日头条:从工程搭建到数据入库实战

Java批量抓取百度新闻与今日头条:从工程搭建到数据入库实战 简介面向Java开发者的新闻爬虫示例聚焦百度新闻与今日头条的站内抓取场景支持按指定关键字批量采集新闻标题、正文、发布时间等字段并统一写入数据库适合数据收集、新闻聚合或爬虫入门练习。压缩包体积仅19KB共20个文件其中十六个Java源码文件按功能划分构建了从网址管理、页面请求、内容解析到数据入库的完整链路一个XML与一个properties文件负责依赖或参数配置另附README说明与gitignore文件便于快速导入本地工程。已有448人学习浏览资源轻量但覆盖爬虫核心流程网址队列收集、HTTP请求发起、正则或XPath定位、数据持久化同时涉及robots协议遵守与访问频率限制等反爬应对思路。读者可基于此快速搭建新闻采集原型并根据自身需求扩展多站点支持或分布式抓取也可作为Java网络编程与数据采集入门的参考。1. 用 Java 按关键字批量抓取百度新闻与今日头条这份资源能解决什么做新闻舆情监控、内容聚合或垂直信息库的人最常卡在两个点上一是目标站点的数据怎么稳定地拿下来二是拿下来的数据往哪里放、怎么查。这份“百度新闻、今日头条爬虫”的 Java 项目把这两件事都串成了一条完整链路——按关键字抓取新闻标题、来源、发布时间和链接再写入数据库。它不是那种用 Python 脚本三五行抓完就丢给 Excel 的玩具而是带着清晰的工程结构Maven 管理依赖、Spring Boot 组织服务、MySQL 承接落库抓取、解析、存储、去重都分模块适合想用 Java 技术栈正经做数据收集的人。不管你是刚学完 Java 想练手完整爬虫流程还是需要快速给分析团队攒一批新闻明细数据都可以拿这份资源当起点。有一点先讲清楚任何爬虫都必须控制频率、遵守目标站点的访问规则这份资源适合个人学习与低频率数据整理别拿它怼着别人的服务器跑。2. 项目结构与选型从 pom.xml 到程序入口看看这套爬虫怎么组织2.1 资源内的目录逻辑先看这几个文件再动手拿到压缩包解压开后典型的一层 Maven 工程结构外层是SJT-code内部有pom.xml、src/main、.gitignore和README.md。初学者最容易犯的错是一上来就点开某个.java文件狂看忽略了对整个工程影响最大的pom.xml。这个文件决定了依赖能不能拉下来、编译级别够不够、最终能不能启动。我一般会按下面顺序拆打开pom.xml第一件事确认三样parent或packaging类型、JDK 编译版本maven.compiler.source/target、核心依赖清单。对于这类爬虫项目常见依赖包括spring-boot-starter-web提供 Web 和任务调度能力、org.jsoup:jsoup解析 HTML、org.apache.httpcomponents:httpclient发 HTTP 请求、mysql-connector-java数据库驱动、lombok简化实体类、fastjson或jackson解析头条接口的 JSON 数据。依赖版本之间最容易打架比如 Spring Boot 2.x 和 mysql-connector-java 5.x 配合是没问题的但换成 MySQL 8 驱动就必须确认 URL 里要不要拼serverTimezone。再看src/main下的包结构。这类项目通常是按职责分包而不是按技术分层crawler爬虫抓取与解析、service业务编排、dao或mapper数据库操作、config配置类。分包越清晰你后面替换解析逻辑或切换数据源越省事。拿这份资源来说你完全可以不改动 service 层只替换百度或头条的解析器让它去抓其他同类新闻站点。最后粗略扫一眼README.md但这份项目的 README 可能写得很简真正的运行说明反而要靠你从 pom.xml 和 application 配置里反推。2.2 为什么是 Java 而不是 Python选型理由与技术点很多人觉得新闻爬虫就该用 Python毕竟requests加xpath三行就能抓一个页面。但把这个逻辑放到工程里差别就出来了。Java 生态的爬虫项目抓取和存库可以住在一个进程里Spring Boot 自带定时任务、事务管理和连接池解析完一条新闻直接交给 MyBatis 批量插入整个过程有类型约束、有异常链路、有日志埋点上了生产环境也比脚本好维护。用 Python 做数据探索确实最短路径但要接到企业现有的 Java 系统里要么起独立服务要么靠调度脚本拼装反而多了一道工序。这份项目的技术栈也踩在 Java 爬虫的主流路线上HTTP 请求走HttpClientHTML 解析走JsoupJSON 解析走Fastjson或Jackson存储走 MySQL。整套选型有一个很实际的理由——百度新闻搜索结果是 HTML 页面正文信息和链接都嵌在 DOM 里适合用 Jsoup 的选择器语法它和 jQuery 选择器几乎同名写起来顺手头条的搜索结果页是前后端分离数据从接口返回 JSON适合 Jackson 直接反序列化成对象。一个项目同时覆盖两种主流抓取方式这对想复用代码的人来说是最有价值的地方。2.3 从关键字到入库主流程设计与关键等待时间整个爬虫业务的主流程可以用一句话概括读关键字 → 构造目标站点 URL → 发起请求 → 解析页面/接口 → 提取新闻字段 → 去重 → 写入数据库 → 等待一段时间再抓下一个关键字。下面这段伪代码是这类项目最常见的编排方式你拿到的资源大概率也是这个结构只是类名和接口名可能不同public void crawlByKeywords(ListString keywords) { for (String keyword : keywords) { try { ListNewsItem baiduList baiduCrawler.fetchByKeyword(keyword); newsService.batchSave(baiduList); Thread.sleep(3000); // 每个关键字之间停 3 秒 ListNewsItem toutiaoList toutiaoCrawler.fetchByKeyword(keyword); newsService.batchSave(toutiaoList); Thread.sleep(3000); } catch (Exception e) { log.error(keyword [{}] crawl failed, keyword, e); } } }逻辑不复杂但里面有三个细节值得注意。第一batchSave方法的幂等性很关键同一批数据重复抓取时必须被数据库拦下来而不是每次插入都报主键冲突。第二Thread.sleep(3000)被称为“犯罪时间”也不夸张固定 3 秒容易被目标站点识别成机器更稳妥的做法是3000 new Random().nextInt(2000)随机睡眠。第三爬虫程序最忌讳一个关键字抛异常就中断整体任务上面这段把异常捕获放在关键字循环内部能保证单个关键词失败不影响后续批次。这个编排逻辑可以在你拿到手后直接沿用两个爬虫模块各自维护自己的抓取逻辑互不干扰。3. 实现百度新闻抓取关键字 URL 模板与入库包含字段设计3.1 构造百度新闻搜索 URL编码是关键别只拼一个字符串百度新闻在 PC 端的搜索地址常用的是https://www.baidu.com/s?tnnewsword关键字tnnews表示走新闻频道而不是通用网页搜索。构造 URL 时有个十有八九会犯的错直接用中文关键字拼字符串。HTTP 请求里携带中文必须经过百分号编码Java 里标准做法是用URLEncoder.encode否则你发出去的请求要么 400要么服务端收到乱码后返回空结果。下面这段是这类项目里最通用的写法String baseUrl https://www.baidu.com/s; String encodedKeyword URLEncoder.encode(keyword, StandardCharsets.UTF_8.name()); String url baseUrl ?tnnewsword encodedKeyword;注意URLEncoder.encode静态方法会声明抛UnsupportedEncodingException在现代 JDK 里你直接传StandardCharsets.UTF_8.name()即可也可以用java.net.URL的构造器配合查询参数类来做编码但上面这种字符串拼接依然是爬虫项目里最常见、最透明的做法。参数说明tnnews是新闻频道标志word是搜索关键字如果还想限制时间范围可以追加rtt1表示按时间排序或者追加上游资讯平台的筛选参数但前提是你先拿浏览器打开这个 URL 验证一下它能返回你想要的页面结构。关键字里如果带空格或编码后会出现或%20百度搜索能正常处理不必过度处理。3.2 Jsoup 解析搜索结构选择器怎么配、空值怎么兜底请求拿到 HTML 之后解析层就是整个项目的“主战场”。百度新闻搜索结果页每条结果通常包裹在一个带固定 class 的容器块里标题在h3下的a标签中摘要和来源在相邻的段落里。不过我要先给个心理建设百度页面的 class 命名不是稳定的改版频繁不要盲信网上贴出来的选择器拿到手第一件事是打开浏览器、F12 找到真实结构再对号入座。下面是常见的解析骨架Document doc Jsoup.connect(url) .userAgent(Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36) .timeout(8000) .get(); Elements items doc.select(div.result); for (Element item : items) { Element titleLink item.selectFirst(h3 a); Element summaryEl item.selectFirst(div.c-summary, span.c-color-text); if (titleLink null) { continue; // 标题解析不到这条直接跳过不要让它带脏数据入库 } String title titleLink.text(); String jumpUrl titleLink.absUrl(href); String summary summaryEl null ? : summaryEl.text(); }解析逻辑有三个容易踩的位置。第一select返回空集合是常态尤其是目标页面改版后每次解析前做null判断或空集合判断比在数据库里做数据清洗省事得多。第二titleLink.absUrl(href)会把相对路径自动拼接成绝对地址这个比手动拼要好因为百度搜索结果的链接有时候是//www.baidu.com/link?urlxxx这种协议相对写法。第三摘要节点可能在页面上有多个结构版本用逗号分隔多个选择器让 Jsoup 从中匹配任意一个是应对页面改版最省事的兜底。解析出来的标题、URL、摘要再结合外部传入的关键字和当前系统时间就是一个完整的NewsItem对象。3.3 数据库建表与带头去重复字段名设计的好坏直接决定后续多省心新闻数据入库建表语句不能拍脑袋。抓下来的一条新闻至少要记录关键字、标题、来源、发布时间、摘要、原始链接这几项。下面这份建表 SQL 是这类新闻数据最常用的结构我在多个项目里验证过CREATE TABLE news_data ( id BIGINT PRIMARY KEY AUTO_INCREMENT, keyword VARCHAR(128) NOT NULL, title VARCHAR(512) NOT NULL, source VARCHAR(256), publish_time DATETIME, summary VARCHAR(1000), url VARCHAR(1024) NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_keyword_url (keyword, url) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;表结构里有几个设计点很关键。UNIQUE KEY uk_keyword_url (keyword, url)把“关键字 URL”作为唯一索引这是新闻爬虫的去重基建。为什么要带keyword因为同一条新闻可能被“比特币”和“区块链”两个关键字同时搜到如果不带关键字去重第二次抓取会被数据库挡下来你按关键字统计时就少了一条关联数据。带关键字去重后细节上是允许同一条 URL 出现在不同关键字结果的这符合业务直觉。URL 字段为什么定1024新闻外链经常带一堆统计参数几百个字符是常态。字符集选utf8mb4而不是utf8是为了兼容新闻标题里可能出现的表情符号和生僻字。数据插入时在 MyBatis 里直接使用INSERT IGNORE配合唯一索引一行代码就把重复问题消化在数据库层面// mapper 接口方法对应 SQLINSERT IGNORE INTO news_data (...) VALUES (...) int inserted newsDataMapper.insertIgnoreBatch(itemList); log.info(本次实际新增 {} 条, inserted);INSERT IGNORE返回的影响行数是“真正新增”的条数不是尝试插入的总数这个返回值可以用来判断当批数据的重复率。重复率太高时先反思是不是请求间隔太短导致页面结构没变化或者关键字列表重复了。数据入库后按关键字分组统计、按时间范围查询都属于常规操作但注意字段名别踩保留字的坑具体我在第五章展开。4. 今日头条抓取与反爬应对接口返回、请求头伪装与抓取频率4.1 头条的数据源在接口里不在 HTML 里今日头条的电脑端页面是典型的前后端分离应用直接抓 HTML 的话你会得到一大坨 Vue 初始化脚本和空荡荡的 DOM正文内容一个都见不着。这类网站的正确打开方式是找到它背后的搜索数据接口。头条的搜索接口返回结构通常是 JSON包含新闻标题、来源、发布时间、内容摘要和跳转链接。requests库能做的Java 的HttpClient一样能做区别只是响应体解析方式不同。实际抓的时候先别急着写解析代码在浏览器里打开开发者工具Network 面板里找到search/list或search相关的请求看它的Preview标签页返回的 JSON 里有哪些字段——这一步能帮你省掉两个小时瞎摸索。接口请求的 URL 构造方式和百度搜索类似关键字参数需要 URL 编码后再拼进去以头条搜索接口为例你通常能拿到接近这样的地址https://www.toutiao.com/api/search/list/?keyword关键字同时在请求头里带上页面的必要信息。注意头条的接口当前一般会带动态签名参数它不是固定的拼接字符串能直接触发的所以这份资源如果只是给了基础请求框架你需要在本地先观察接口实际请求头与参数格式。我的习惯是把返回体先打印一份保存成 JSON 文件对照真实数据再去设计实体类而不是凭空定义字段。头条新闻列表里同一篇文章可能在不同位置重复出现靠group_id或者item_id做去重比靠标题去重可靠得多。4.2 请求头伪装UA、Referer、Accept-Language 三个要配齐很多爬虫失败的原因不是 IP 被封而是请求头暴露了脚本身份。发出去的请求服务器会先看 User-Agent如果是空值或Java/1.8这种默认 UA直接拒绝的概率非常大。头条这类站点对 UA 比较敏感更完整的做法是配一组常见的浏览器 UA 字符串每次请求随机抽一个用。除了 UAReferer 和 Accept-Language 也是容易被忽略的两个字段。不设 Referer 时部分服务器会认为这是外部冷请求Accept-Language缺失某些国内站点返回的内容编码判断会出问题。下面是用 Apache HttpClient 构造头条请求的常见模板CloseableHttpClient client HttpClients.custom() .setDefaultRequestConfig(RequestConfig.custom() .setConnectTimeout(5000) .setSocketTimeout(8000) .build()) .build(); HttpGet get new HttpGet(url); get.setHeader(User-Agent, Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0 Safari/537.36); get.setHeader(Referer, https://www.toutiao.com/); get.setHeader(Accept, application/json, text/plain, */*); get.setHeader(Accept-Language, zh-CN,zh;q0.9); get.setHeader(Cookie, 你的浏览器登录态 Cookie可选);参数说明setConnectTimeout是建立 TCP 连接的超时时间头条接口偶发慢响应5 秒比较稳妥setSocketTimeout是等待响应的超时时间8 秒够用且不会让程序长时间挂死。Cookie 这里要特别说明冷启动时没带登录态也能拿到部分公开搜索数据但如果接口返回的 JSON 里出现验证页特征说明当前需要登录态或触发了风控。这时候不要靠“多试几次”去撞正确做法是降低整体抓取频率、将任务分散到多个时间段执行或者评估是否改用官方开放能力。爬虫的价值在于整理公开数据而不是对抗别人的防护机制。4.3 频率控制与重试这段“刹车代码”才是项目能不能长期跑的关键头条接口对频率的容忍度比百度新闻低这是实战里最直观的血泪经验。第一次跑这类项目我习惯把每次请求的间隔压在 “上一次请求结束到下一次请求开始” 的 2 到 5 秒随机范围内。写请求代码时顺手把“正在抓哪个关键字、当前是第几页、已抓多少条”全部打进日志这样一旦被封你能立刻定位是哪一步触发了风控。下面这段重试逻辑是对付偶发 5xx 和网络抖动的常用写法int retryTimes 0; boolean success false; while (retryTimes 3 !success) { try { CloseableHttpResponse response client.execute(get); int statusCode response.getStatusLine().getStatusCode(); if (statusCode 200) { // 这里先读取 response.getEntity() 并做 JSON 解析 success true; } else if (statusCode 403) { log.warn(触发风控等待 30 秒后重试); Thread.sleep(30_000); } else { Thread.sleep(5_000L * (retryTimes 1)); } } catch (IOException e) { log.warn(请求异常重试中, e); Thread.sleep(3_000L * (retryTimes 1)); } retryTimes; }这段代码最值得学习的地方是“失败时等待时间递增”的退避策略指数退避的简化版。第一次失败等 5 秒第二次失败等 10 秒第三次失败等 15 秒给服务器留出恢复窗口而不是死命重试加速封禁。403单独处理成等待 30 秒是因为它通常意味着你的请求频率已经触发了风控短时间重试毫无意义。真实生产里我还会加一个熔断开关连续 5 次 403 就暂停整个任务并给负责人发告警因为继续跑下去只会让 IP 被封得更久。头条这类站点每天换着时间段跑比一口气全量跑完更安全这属于爬虫工程师的“玄学”但确实有效。5. 避坑排查启动失败、乱码、字段撞名与触发风控的五个修复记录5.1 项目拿回来跑不起来依赖源和 JDK 版本两个原因现象mvn spring-boot:run启动时报错控制台红字提示无法解析依赖或编译失败。新手最容易在这地方卡住转头就去联系资源提供方其实超过一半的问题出在本地环境。原因分两类。一是 Maven 默认从中央仓库下载依赖国内网络环境经常拉一半超时导致.m2目录里残留损坏的 jar二是 pom 里声明的编译版本比如 1.8和你本地装的 JDK 版本不匹配。解决办法也很直接打开~/.m2/settings.xml配置阿里云镜像仓库把mirrorOf设为central然后跑java -version和mvn -v确认 JDK 版本。如果本地是 JDK 17而 pom 要求 1.8需要安装一个对应版本的 JDK或者在 pom 的maven.compiler.source和target上改成兼容的版本。这类项目我建议先跑一遍mvn clean package -DskipTests跳过测试只做编译编译通过了再谈运行。等你自己把这些基础环境排查完还是没法启动那时候再考虑联系资源方也不迟。5.2 中文乱码MySQL 表和连接串两侧都要顾现象日志里打印的标题是中文一切正常落库之后再查全是???或者乱码符号。这个坑几乎人人都踩。原因有两个且常常同时发生数据库表本身字符集不是utf8mb4JDBC 连接串里没带编码参数。只改表字符集连接层还是按 latin1 写入照样乱码。解决方法是双管齐下建表时在表定义末尾加DEFAULT CHARSETutf8mb4已经建错的表可以用ALTER TABLE news_data CONVERT TO CHARACTER SET utf8mb4修复同时检查application.yml里的连接串spring: datasource: url: jdbc:mysql://localhost:3306/news_db?useUnicodetruecharacterEncodingutf8mb4serverTimezoneAsia/Shanghai这几个参数useUnicodetrue和characterEncodingutf8mb4是中文数据入库的保险栓serverTimezoneAsia/Shanghai解决插入publish_time时的小时差问题。修改完连接串重启服务新写入的数据就正常了。已经乱掉的历史数据只能删除重抓所以在正式启动前先抓一条数据验证中文链路。5.3 建表或插入时 SQL 语法报错字段名撞了 MySQL 保留字现象执行 SQL 时报错大概位置在keyword、source或order附近的语法错误。这段报错对没经验的人来说非常劝退看着是标准 SQL 但就是过不去。原因MySQL 有一些保留字不能直接用作表名字段名而“关键字”“来源”这类词恰恰容易撞车。这里的keyword就是典型撞名。解决办法有两个一是建表时给每个可能冲突的字段加反引号包裹例如keyword VARCHAR(128) NOT NULL二是更推荐直接改字段名把keyword改成search_keyword把source改成source_name从根上消除隐患。爬虫项目建表时你还会遇到content、describe这类潜在冲突词统一加前缀或者建表时在 MySQL 客户端里先试跑一遍比落到 Java 代码里再去查错快得多。5.4 存进库里的 URL 全是百度的跳转中间页现象url字段存下来的链接长这样https://www.baidu.com/link?urlxxxxx点开能跳转到真实新闻页但直接抓正文源站时这个链接不可用。原因百度搜索结果页里的链接不是新闻源站的直接地址而是经过百度跳转服务包装的。数据入库时如果直接取href存下来的就是这个中间链接。解决方式是在解析层做一层“还原”。常见做法有两种第一种解析baidu.com/link?url后面的参数结合重定向逻辑拿到真实外链第二种用HttpClient发一个HEAD请求跟随重定向落点拿最终地址。但注意这会增加一次额外请求频率上要更克制。如果你只用这份数据做标题摘要层面的统计不打算抓正文那存跳转链接也可以接受但如果后续要做源站正文采集务必在入库前还原成真实外链。5.5 爬了百十条突然被限制固定 UA 和零间隔是最大诱因现象前 100 条抓得很顺利然后突然连续 403或者页面返回里出现验证码特征再往后数据量就停在原地不动了。原因请求特征太像程序。固定一个 UA、请求间隔恒定 3 秒、串行循环不间断跑这些信号叠加起来服务器判断你是脚本的概率非常高。解决办法是把请求参数“做散”维护一个 5 到 10 个浏览器 UA 的列表每次请求随机取一个请求间隔改成2000 到 5000毫秒随机如果确实急需抓完可以配置多组代理但代价是代理稳定性不可控。还有一个容易被忽略的点每次请求的目标页面不要共用同一个HttpClient实例长连接部分站点的反爬会检测到 HTTP 连接复用特征定时重建客户端能有效规避。最简单直接的止损操作是连续出现三次异常状态码就暂停本轮任务人工确认页面结构是否变化再决定要不要继续。6. 增量抓取与数据验证让爬虫从一次性脚本变成可靠的数据来源6.1 增量去重好数据库结构应该让重复抓取自动失效爬虫项目一旦跑起来最大的烦恼不是抓不到数据而是重复数据越来越多。我在前面建的唯一索引uk_keyword_url已经解决了大部分重复问题这里再补一个更精确的时间过滤只抓“今天”的新闻。百度新闻搜索本身就支持按时间范围筛选在 URL 上追加时间参数就能减少大量无效抓取头条接口也在结果里返回publish_time在入库前判断如果发布时间不在目标窗口内就跳过。这样做的直接好处是你每天定时执行抓取库里的数据始终是最近几天的新文章不是越滚越大的膨胀库。增量逻辑不要写到 SQL 里和数据库较劲放在解析层用时间戳过滤是最轻松的。每天定时跑一遍我用的是 Spring 自带的调度注解配置如下Component public class NewsCrawlScheduler { Scheduled(cron 0 30 8 * * ?) public void dailyCrawl() { ListString keywords Arrays.asList(比特币, 人工智能, 新能源); crawlService.crawlByKeywords(keywords); } }0 30 8 * * ?表示每天早上 8 点 30 分执行一次。这个时间点避开业务高峰期对站点服务器也更友好。需要定时跑多个关键字就把列表维护在配置文件里外面改配置不需要重新编译。6.2 抓完以后怎么验证数据质量三条 SQL 看清全局爬虫跑完不能看一眼日志就收工。我每次都会从三个角度抽查数据总量、关键字分布、发布时间分布。下面三条 SQL 是我固定会执行的-- 1. 总量是否合理太少了可能页面解析失败太多了可能关键字重复 SELECT COUNT(*) FROM news_data; -- 2. 按关键字看分布如果某个关键字结果异常少单独检查它的抓取日志 SELECT search_keyword, COUNT(*) FROM news_data GROUP BY search_keyword; -- 3. 按发布日期看最近趋势确认多数文章集中在最近两天 SELECT DATE(publish_time) AS d, COUNT(*) FROM news_data GROUP BY DATE(publish_time) ORDER BY d DESC LIMIT 14;如果第一条返回的数字接近 0先不要怀疑目标网站没数据多半是解析选择器失效。此时用浏览器打开目标搜索地址查看页面结构是否新增了data属性或改了 class 名。如果第三条的发布时间全是 Null很可能接口返回的时间字段不是标准时间格式需要在解析层额外做一次时间格式化转换再写入。6.3 把这份爬虫资源用稳的最后一块拼图写爬虫项目最忌讳的是把代码写完了就当它跑得好。我的习惯是每次新接入一个站点或改动一个解析规则先只抓取一个关键字、限制只存储 10 条人工检查这 10 条数据的标题是否完整、URL 是否能打开、发布时间是否正确。确认没有异常后再放开到全量关键字。这套“小批量验证”的习惯救了我很多次——以前我有一次直接拿着跑全量结果百度页面刚好改版一个选择器写错批量抓下来的 3000 条数据里 2800 条标题都是“百度安全验证”整个数据库直接没法用。从那以后我每次启动爬虫任务都强制先走一遍小批量验证看到日志里标题正常、入库条数合理再放开全部关键字。希望这套流程帮你也少踩一些坑拿到这份 Java 新闻爬虫项目后改好数据库连接和关键字列表用这一套方法稳稳跑起来。本文还有配套的精品资源点击获取
返回列表