安卓脚本软件
标题:
豆包生成文章ADB 方式采集问答内容对接 MySQL 入库
[打印本页]
作者:
信息发布软件
时间:
昨天 06:31
标题:
豆包生成文章ADB 方式采集问答内容对接 MySQL 入库
豆包生成文章ADB 方式采集问答内容对接 MySQL 入库
/**
* 案例 12:豆包生成文章 + ADB 方式采集问答内容 + 对接 MySQL 入库
*
* 只教一件事:怎么用 ADB(uiautomator dump)把豆包对话页的问答文本整页抓下来,
* 攒成列表,再通过 SDK 的 mysql 模块直连数据库逐条入库。
*
* 流程(每个主题一轮):
* 打开豆包 → 输入框输入"写一篇 XX 的文章"并发送 → 轮询 dump 等生成稳定
* → 对比发送前后的页面文本,新出现的长文本就是 AI 回答
* → push 进内存列表 + INSERT 进 MySQL
*
* 采集原理:
* adb.exec("uiautomator dump /sdcard/qa_dump.xml") 把当前界面控件树导出成 XML,
* 再 adb.exec("cat ...") 读回来,正则抠所有 text="..." —— 一次拿到整页文字,
* 不用逐个控件找。若无障碍服务和 uiautomator 冲突导致 dump 失败,
* 自动降级用 auto.getXml()(AIWROK 自己的控件树,同样能抠 text)。
*
* 配置说明:
* 豆包安卓包名:com.larus.nova(应用宝确认)
* DB_MODE="sqlite"(默认):设备本地生成 /sdcard/qa_articles.db,零配置直接能跑;
* DB_MODE="mysql":DB_URL 要填"模拟器能访问到"的 MySQL 地址(127.0.0.1 是模拟器自己,
* 连不上宿主机),且 URL 必须带 connectTimeout/socketTimeout,否则连不上会无限卡死。
* 数据库不可用时不中止,降级为只采集进内存列表。
*
* 运行:雷电模拟器 + 豆包已登录 + 无障碍开启;不用 HID、不用截图
* (本脚本无截图对象,若日后加了 screenShot,记得最后 .recycle() 释放)。
*/
// ==================== 配置(改这里)====================
var DB_MODE = "sqlite"; // "sqlite"=设备本地库文件(零配置,默认) | "mysql"=远程 MySQL
var PKG = "com.larus.nova"; // 豆包
var DB_URL = "jdbc:mysql://192.168.1.100:3306/aiwork?useSSL=false&characterEncoding=utf8&connectTimeout=5000&socketTimeout=10000"; // 改成你的 MySQL(必须带超时,否则连不上会无限卡死)
var DB_USER = "root";
var DB_PASS = "请填写数据库密码";
var SQLITE_PATH = "/sdcard/qa_articles.db"; // SQLite 模式:本地库文件路径
var DB_TABLE = "qa_articles";
var TOPICS = ["春天的景色", "人工智能改变生活", "健康饮食习惯"]; // 要生成文章的题目列表
var POLL_MS = 3000; // 生成期间 dump 轮询间隔
var MIN_GEN_MS = 35000; // 最短等待:豆包生成一篇 800 字文章实测要 35 秒左右,之前 12 秒就误判"稳定"
var MAX_WAIT_MS = 90000; // 单篇最长等待生成时间
var STEP_WAIT = 3000; // 每篇入库后的缓冲
// ==================== 全局变量放顶部 ====================
importClass(android.database.sqlite.SQLiteDatabase);
importClass(java.io.File);
var gStop = false;
var gConn = null;
var gDumpWarned = false; // adb dump 不可用只提示一次
var gList = []; // 采集到的问答列表 [{topic, question, answer, time}]
// ==================== 工具 ====================
function doSleep(ms) { sleep.millisecond(ms); }
function isStop() { return gStop; }
event.onStopBtnEvent(function () {
gStop = true;
printl("[停止] 收到停止请求,收尾后退出");
});
// ==================== 数据库:sqlite(本地文件) / mysql(JDBC) 双模式 ====================
function dbConnect() {
if (DB_MODE == "mysql") {
printl("[DB] 正在连接 MySQL(5 秒超时)...");
try {
gConn = mysql.getConnection(DB_URL, DB_USER, DB_PASS);
printl("[DB] ✅ MySQL 连接成功");
return true;
} catch (e) {
printl("[DB] ❌ MySQL 连接失败: " + e);
return false;
}
}
// sqlite:设备本地库文件,零配置
try {
gConn = SQLiteDatabase.openOrCreateDatabase(new File(SQLITE_PATH), null);
printl("[DB] ✅ SQLite 已打开: " + SQLITE_PATH);
return true;
} catch (e2) {
printl("[DB] ❌ SQLite 打开失败: " + e2);
return false;
}
}
function dbEnsureTable() {
var sql;
if (DB_MODE == "mysql") {
sql = "CREATE TABLE IF NOT EXISTS " + DB_TABLE + " (" +
"id INT AUTO_INCREMENT PRIMARY KEY," +
"topic VARCHAR(255)," +
"question TEXT," +
"answer LONGTEXT," +
"collected_at VARCHAR(32)" +
") DEFAULT CHARSET=utf8mb4";
} else {
sql = "CREATE TABLE IF NOT EXISTS " + DB_TABLE + " (" +
"id INTEGER PRIMARY KEY AUTOINCREMENT," +
"topic TEXT, question TEXT, answer TEXT, collected_at TEXT)";
}
try {
gConn.execSQL(sql);
printl("[DB] 表 " + DB_TABLE + " 就绪");
return true;
} catch (e) {
printl("[DB] ❌ 建表失败: " + e);
return false;
}
}
function dbInsert(item) {
try {
var sql = "INSERT INTO " + DB_TABLE +
" (topic, question, answer, collected_at) VALUES (?,?,?,?)";
if (DB_MODE == "mysql") {
var stmt = gConn.prepareStatement(sql);
stmt.setString(1, item.topic);
stmt.setString(2, item.question);
stmt.setString(3, item.answer);
stmt.setString(4, item.time);
var rows = stmt.executeUpdate();
stmt.close();
printl("[DB] ✅ 入库 " + rows + " 条:《" + item.topic + "》");
} else {
var stmt2 = gConn.compileStatement(sql);
stmt2.bindString(1, item.topic);
stmt2.bindString(2, item.question);
stmt2.bindString(3, item.answer);
stmt2.bindString(4, item.time);
stmt2.executeInsert();
stmt2.close();
printl("[DB] ✅ 入库成功:《" + item.topic + "》");
}
return true;
} catch (e) {
printl("[DB] ❌ 入库失败: " + e);
return false;
}
}
function dbClose() {
try {
if (gConn != null) { gConn.close(); gConn = null; printl("[DB] 已断开连接"); }
} catch (e) {}
}
// ==================== ADB 采集:整页 dump → 抠文本 ====================
// 优先 adb uiautomator dump;失败降级 auto.getXml()
function dumpPageXml() {
var xml = "";
try {
adb.exec("uiautomator dump /sdcard/qa_dump.xml", false);
xml = adb.exec("cat /sdcard/qa_dump.xml", false);
} catch (e) {}
if (xml == null || xml.indexOf("<hierarchy") < 0) {
if (!gDumpWarned) { gDumpWarned = true; printl("[采集] adb dump 不可用(与无障碍服务冲突),后续统一用 auto.getXml()"); }
try { xml = auto.getXml(); } catch (e2) { xml = ""; }
}
return xml == null ? "" : xml;
}
// 抠 XML 里所有 text="..." 的非空文本(顺带还原常见转义符)
function extractTexts(xml) {
var out = [];
var re = /text="([^"]*)"/g;
var m;
while ((m = re.exec(xml)) != null) {
var t = m[1];
if (t != null && t.length > 1) {
t = t.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">")
.replace(/"/g, '"').replace(/
/g, "\n");
out.push(t);
}
}
return out;
}
// 页面文本指纹:用来判断生成有没有"写完"(文本不再变 = 完成)
function pageSignature(texts) {
return texts.join("#");
}
// 发送前后的文本做差:新出现的长文本就是 AI 的回答
function pickAnswer(baseTexts, afterTexts) {
var best = "";
for (var i = 0; i < afterTexts.length; i++) {
var t = afterTexts[i];
if (t.length < 30) continue; // 回答正文至少 30 字,短的是按钮/标题
var isNew = true;
for (var j = 0; j < baseTexts.length; j++) {
if (baseTexts[j] == t) { isNew = false; break; }
}
if (isNew && t.length > best.length) best = t;
}
return best;
}
// ==================== 豆包操作:输入 → 发送 → 等生成稳定 ====================
function askDoubao(promptText) {
// 1) 找输入框:先标准 EditText,再 editable 兜底
var nodeObj = null;
try {
var ed = auto.nodeSearch(3000).className("android.widget.EditText");
if (ed != '' && ed.count() > 0) nodeObj = ed.getNode(0);
} catch (e) {}
if (nodeObj == null) {
try {
var ed2 = auto.nodeSearch(2000).editable(true);
if (ed2 != '' && ed2.count() > 0) nodeObj = ed2.getNode(0);
} catch (e2) {}
}
if (nodeObj == null) { printl("[输入] ❌ 没找到输入框"); return false; }
nodeObj.click();
sleep.second(1);
nodeObj.input(promptText); // 中文只能走 node.input,adb input text 不支持中文
doSleep(800);
// 2) 发送:文本"发送" → desc"发送" → 回车,三路兜底
var sent = false;
try {
var sb = auto.nodeSearch(1500).text("发送");
if (sb != '' && sb.count() > 0) { sb.getNode(0).click(); sent = true; }
} catch (e) {}
if (!sent) {
try {
var sb2 = auto.nodeSearch(1500).desc("发送");
if (sb2 != '' && sb2.count() > 0) { sb2.getNode(0).click(); sent = true; }
} catch (e2) {}
}
if (!sent) {
try { input.enter(); sent = true; printl("[发送] 没找到发送按钮,用回车发送"); } catch (e3) {}
}
printl("[发送] " + (sent ? "✅ 已发送" : "❌ 发送失败"));
return sent;
}
// 轮询 dump:先等满 MIN_GEN_MS,之后页面文本连续两次不变 → 判定生成完成
function waitForAnswer(baseSig) {
var waited = 0;
var lastSig = "";
var stable = 0;
while (waited < MAX_WAIT_MS && !isStop()) {
sleep.second(POLL_MS / 1000);
waited += POLL_MS;
var sig = pageSignature(extractTexts(dumpPageXml()));
if (waited < MIN_GEN_MS) {
// 35 秒内豆包大概率还在生成,不做稳定判断,避免刚发出去就误判完成
if (waited % 15000 == 0) printl("[生成] 最短等待中...已 " + (waited / 1000) + " 秒");
lastSig = sig;
continue;
}
if (sig != baseSig && sig.length > baseSig.length && sig == lastSig) {
stable++;
if (stable >= 2) {
printl("[生成] 文本已稳定,判定生成完成(用时 " + (waited / 1000) + " 秒)");
return true;
}
} else {
stable = 0;
}
lastSig = sig;
if (waited % 15000 == 0) printl("[生成] 已等待 " + (waited / 1000) + " 秒...");
}
printl("[生成] ⏰ 超时,按当前页面内容采集");
return true; // 超时也照采,有多少算多少
}
// ==================== 主流程 ====================
function main() {
printl("╔════ 案例12:豆包文章生成 + ADB采集入库 ════╗");
printl("[配置] 主题 " + TOPICS.length + " 个 | 表 " + DB_TABLE);
// 数据库连不上不中止:降级为只采集进内存列表
var dbOk = dbConnect();
if (dbOk) dbOk = dbEnsureTable();
if (!dbOk) printl("[DB] ⚠️ 数据库不可用,本次只采集进列表,不入库");
printl("[DB] 数据存放位置: " + (DB_MODE == "mysql" ? DB_URL : SQLITE_PATH));
// 重启豆包,保证从干净对话页开始
app.closeApp(PKG);
doSleep(1500);
app.openApp(PKG);
printl("[启动] 等 15 秒让豆包加载完");
sleep.second(15);
for (var i = 0; i < TOPICS.length; i++) {
if (isStop()) break;
var topic = TOPICS[i];
var promptText = "请写一篇关于《" + topic + "》的文章,800字左右";
printl("──── 第 " + (i + 1) + "/" + TOPICS.length + " 篇:" + topic + " ────");
// 发送前先记基线,回答 = 之后新出现的长文本
var baseTexts = extractTexts(dumpPageXml());
var baseSig = pageSignature(baseTexts);
if (!askDoubao(promptText)) continue;
waitForAnswer(baseSig);
if (isStop()) break;
var afterTexts = extractTexts(dumpPageXml());
var answer = pickAnswer(baseTexts, afterTexts);
if (answer == "") {
printl("[采集] ❌ 没抓到正文(可能生成失败/被折叠),跳过");
continue;
}
var item = { topic: topic, question: promptText, answer: answer, time: time.now() };
gList.push(item); // 内存列表
if (dbOk) dbInsert(item); // 数据库
printl("[采集] 正文 " + answer.length + " 字 | 已存: " + (DB_MODE == "mysql" ? DB_URL : SQLITE_PATH));
printl("──── 《" + topic + "》全文开始 ────");
printl(answer);
printl("──── 《" + topic + "》全文结束 ────");
sleep.second(STEP_WAIT / 1000);
}
// 列表汇总
printl("╔════ 采集列表(共 " + gList.length + " 条)════╗");
for (var k = 0; k < gList.length; k++) {
printl("[" + (k + 1) + "] 《" + gList[k].topic + "》 | " + gList[k].time +
" | " + gList[k].answer.length + " 字");
}
printl("[DB] 全部数据存放位置: " + (DB_MODE == "mysql" ? DB_URL : SQLITE_PATH));
if (dbOk) dbClose();
printl("[结束] 豆包采集流程执行完毕");
}
main();
复制代码
欢迎光临 安卓脚本软件 (https://www.postbbs.com/)
宣传软件