|
|
百度热搜榜定时采集入库关键词监控/排名变动
- /**
- * 案例18:百度热搜榜定时采集入库 + 关键词监控/排名变动报警(纯 printl 日志版,输出到 H5 日志终端)
- *
- * 经典实用案例"热搜榜监控+关键词盯盘提醒",转换成 AIWROK 实现:
- * okHttp 请求百度热搜公开 JSON 接口(top.baidu.com/api/board,无需Key)
- * → 取 Top N 榜单(标题/排名/热度) → SQLite 入库(hot_records 表) → 逐条完整打印到 H5 终端
- *
- * 案例15/16/17 是"数值监控"(价格/温度/汇率),本案例换成"文本榜单监控",
- * 三个新知识点:
- * 1. okHttp.setHeader("User-Agent", ...):部分公开接口会校验 UA,这是第一次用到请求头;
- * 2. 关键词命中报警:标题包含监控词(如"AI")就打 ⚠ 并附链接,做舆情/盯盘提醒的雏形;
- * 3. 排名变动对比:用对象存上一轮榜单,本轮逐个对比出 新上榜/上升/下降/掉榜,
- * 这是"有状态采集"——脚本记得上一轮数据才能算变化,和前面纯数值入库不同。
- *
- * 接口实测说明(电脑直连验证过):
- * https://top.baidu.com/api/board?platform=pc&tab=realtime 返回 50 条,
- * data.cards[0].content[i] 含 word(标题)/index(排名)/hotScore(热度)/desc(摘要)/url(链接)。
- */
- // ========== 配置区(改这里) ==========
- var 监控关键词列表 = ["AI", "小米", "微信"]; // 命中任一关键词就打 ⚠ 报警(改这里盯你自己的词)
- var 取榜单条数 = 10; // 每轮取前 10 条(演示打印量刚好)
- var 排名变动阈值 = 2; // 排名比上一轮变动 >= 2 位提示 ↑/↓
- var 采集间隔秒 = 30; // 每 30 秒采一轮(录像教学用;长期挂机可改 300)
- var SQLite路径 = "/sdcard/热搜榜.db";
- var 表名 = "hot_records";
- // ========== 全局变量顶部声明 ==========
- var gStop = false;
- var 数据库连接 = null;
- var 数据库可用 = false;
- var 累计入库条数 = 0;
- var 上一轮榜单 = {}; // { "标题": 排名 } 首轮为空对象,不做变动对比
- // ========== importClass 只能放最外层 ==========
- importClass(android.database.sqlite.SQLiteDatabase);
- importClass(java.io.File);
- // ========== 工具函数(先定义后调用) ==========
- // 连接 SQLite(零配置;如需远程库可仿照案例14扩展 mysql 分支)
- function dbConnect() {
- try {
- 数据库连接 = SQLiteDatabase.openOrCreateDatabase(new File(SQLite路径), null);
- 数据库可用 = true;
- printl("[DB] ✅ SQLite 已打开: " + SQLite路径);
- } catch (e) {
- 数据库可用 = false;
- printl("[DB] ⚠ 打开失败(本轮只打印不入库): " + e);
- }
- }
- // 建表(存在则跳过):rank/word/hot_score/collected_at
- function dbEnsureTable() {
- if (!数据库可用) return;
- try {
- var sql = "CREATE TABLE IF NOT EXISTS " + 表名 + " (" +
- "id INTEGER PRIMARY KEY AUTOINCREMENT," +
- "rank INTEGER, word TEXT, hot_score INTEGER, collected_at INTEGER)";
- 数据库连接.execSQL(sql);
- printl("[DB] ✅ 表已就绪: " + 表名);
- } catch (e) {
- printl("[DB] ⚠ 建表失败: " + e);
- 数据库可用 = false;
- }
- }
- // 单条榜单入库
- function dbInsertHot(rank, word, hotScore) {
- if (!数据库可用) return false;
- try {
- var stmt = 数据库连接.compileStatement("INSERT INTO " + 表名 +
- " (rank, word, hot_score, collected_at) VALUES (?,?,?,?)");
- stmt.bindLong(1, rank);
- stmt.bindString(2, "" + word);
- stmt.bindLong(3, hotScore);
- stmt.bindLong(4, time.nowStamp()); // nowStamp=毫秒数字;time.now()是格式化字符串不能bindLong
- stmt.executeInsert();
- stmt.close();
- return true;
- } catch (e) {
- printl("[DB] ❌ 入库失败: " + e);
- return false;
- }
- }
- function dbClose() {
- if (数据库可用 && 数据库连接 != null) {
- try { 数据库连接.close(); } catch (e) {}
- 数据库连接 = null;
- }
- }
- // 拉取一轮热搜榜:返回数组 [{排名,标题,热度,链接}],失败返回 null
- function 拉取热搜() {
- var url = "https://top.baidu.com/api/board?platform=pc&tab=realtime";
- try {
- printl("📡 正在请求百度热搜接口(实时榜)...");
- var http = new okHttp();
- http.setTimeOut(5000, 8000, 5000); // 必须设超时,防网络挂死(踩坑经验)
- // 新知识点:setHeader 带浏览器 UA,公开接口常校验 UA(本接口实测不带也通,带上更稳)
- http.setHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0 Safari/537.36");
- printl("📡 等待接口返回(最长8秒)...");
- var text = "" + http.get(url);
- printl("📡 接口已返回,开始解析 JSON...");
- var obj = JSON.parse(text);
- if (!obj || !obj.success || !obj.data || !obj.data.cards || !obj.data.cards[0] || !obj.data.cards[0].content) {
- printl("[API] ⚠ 接口结构异常: " + text.substring(0, 120));
- return null;
- }
- var 全部 = obj.data.cards[0].content;
- var 取数 = 取榜单条数;
- if (取数 > 全部.length) 取数 = 全部.length;
- var 结果 = [];
- for (var i = 0; i < 取数; i++) {
- var 项 = 全部[i];
- var 热度 = parseInt(项.hotScore);
- if (isNaN(热度)) 热度 = 0;
- 结果.push({ 排名: i + 1, 标题: "" + 项.word, 热度: 热度, 链接: "" + 项.url });
- }
- printl("✅ 解析完成,榜单共 " + 全部.length + " 条,本轮取前 " + 结果.length + " 条");
- return 结果;
- } catch (e) {
- printl("[API] ❌ 拉取失败: " + e);
- return null;
- }
- }
- // 逐条完整打印采集结果(演示用:排名/标题/热度全打出来)
- function 打印采集结果(榜单数组) {
- printl("📋 本轮采集结果(百度热搜 Top " + 榜单数组.length + "):");
- for (var i = 0; i < 榜单数组.length; i++) {
- var s = 榜单数组[i];
- printl(" [" + s.排名 + "] " + s.标题 + " | 热度 " + s.热度);
- }
- }
- // 关键词命中报警:标题包含任一监控词就打 ⚠ 并附链接(舆情/盯盘提醒雏形)
- function 检查关键词(榜单数组) {
- var 命中数 = 0;
- for (var i = 0; i < 榜单数组.length; i++) {
- var s = 榜单数组[i];
- for (var k = 0; k < 监控关键词列表.length; k++) {
- var kw = 监控关键词列表[k];
- if (s.标题.indexOf(kw) >= 0) {
- 命中数++;
- printl("⚠ 报警:关键词「" + kw + "」上榜! [" + s.排名 + "] " + s.标题);
- printl(" 🔗 " + s.链接);
- }
- }
- }
- if (命中数 === 0) {
- printl("🔕 关键词检查:Top " + 榜单数组.length + " 未命中监控词(" + 监控关键词列表.join("/") + ")");
- }
- }
- // 排名变动对比:与上一轮榜单比,算出新上榜/上升/下降/掉榜(有状态采集)
- function 检查排名变动(榜单数组) {
- var 本轮集合 = {};
- for (var i = 0; i < 榜单数组.length; i++) {
- var s = 榜单数组[i];
- 本轮集合[s.标题] = s.排名;
- var 旧排名 = 上一轮榜单[s.标题];
- if (旧排名 == null) {
- printl("🆕 新上榜:[" + s.排名 + "] " + s.标题);
- } else {
- var 变动 = 旧排名 - s.排名; // 正数=名次前进
- if (变动 >= 排名变动阈值) {
- printl("📈 上升:[" + 旧排名 + "→" + s.排名 + "] " + s.标题);
- } else if (-变动 >= 排名变动阈值) {
- printl("📉 下降:[" + 旧排名 + "→" + s.排名 + "] " + s.标题);
- }
- }
- }
- // 掉榜检测:上一轮有、本轮没有
- for (var 词 in 上一轮榜单) {
- if (本轮集合[词] == null) {
- printl("🚪 掉榜:" + 词);
- }
- }
- 上一轮榜单 = 本轮集合; // 本轮存为下一轮对比基准
- }
- // ========== 主流程 ==========
- printl("===== 案例18 百度热搜定时采集入库 启动 =====");
- // 步骤0:连库建表
- dbConnect();
- dbEnsureTable();
- printl("⚙ 配置:取前 " + 取榜单条数 + " 条 · 监控词(" + 监控关键词列表.join("/") + ") · 排名变动阈值 " +
- 排名变动阈值 + " 位 · 采集间隔 " + 采集间隔秒 + " 秒");
- // 停止按钮清理(顺序:先置标志,stopAllRuning 放最后)
- event.onStopBtnEvent(function () {
- gStop = true;
- dbClose();
- runTime.stopAllRuning();
- });
- // 步骤1:定时循环采集
- var 轮次 = 0;
- while (!gStop) {
- 轮次++;
- printl("\n---- 第 " + 轮次 + " 轮采集 ----");
- var 榜单数组 = 拉取热搜();
- if (榜单数组 != null && 榜单数组.length > 0) {
- // 1) 逐条完整打印采集结果(演示重点:榜单标题全打出来)
- 打印采集结果(榜单数组);
- // 2) 入库
- var 本轮入库 = 0;
- printl("💾 开始逐条写入数据库(表: " + 表名 + ")...");
- for (var k = 0; k < 榜单数组.length; k++) {
- var q = 榜单数组[k];
- if (dbInsertHot(q.排名, q.标题, q.热度)) {
- 本轮入库++;
- 累计入库条数++;
- printl(" ✓ 已入库 [" + 本轮入库 + "/" + 榜单数组.length + "] [" + q.排名 + "] " + q.标题);
- } else {
- printl(" ✗ 入库失败: " + q.标题);
- }
- }
- printl("[DB] 本轮入库 " + 本轮入库 + " 条 | 累计 " + 累计入库条数 + " 条 | 库文件: " + SQLite路径);
- // 3) 关键词报警 + 排名变动对比
- 检查关键词(榜单数组);
- if (轮次 === 1) {
- printl("ℹ 首轮已记录榜单基准,从第 2 轮起显示 新上榜/上升/下降/掉榜");
- }
- 检查排名变动(榜单数组);
- } else {
- printl("⚠ 本轮无数据,跳过入库(" + time.StampToTime(time.nowStamp()) + ")");
- }
- // 分片等待下一轮:每 5 秒打一条进度日志(画面不空,方便观察/录像)
- printl("⏳ 本轮全部完成," + 采集间隔秒 + " 秒后自动开始下一轮");
- var 剩余 = 采集间隔秒;
- while (剩余 > 0 && !gStop) {
- var 步长 = 剩余 >= 5 ? 5 : 剩余;
- printl("⏳ 等待下一轮...剩余 " + 剩余 + " 秒(期间脚本在待机,到点自动拉热搜→入库→对比排名)");
- sleep.second(秒 = 步长);
- 剩余 = 剩余 - 步长;
- }
- }
- dbClose();
- printl("===== 流程结束:共 " + 轮次 + " 轮,累计入库 " + 累计入库条数 + " 条 =====");
复制代码
|
|