fix(macOS): stabilize command-line execution path

This commit is contained in:
Sora
2026-08-24 00:37:37 +08:00
parent 9c1ccac473
commit fce434d960
4 changed files with 203 additions and 12 deletions
+1
View File
@@ -16,6 +16,7 @@ execution_instances/*
info.log
npminstall-debug.log
mysql_config.json
macos_command_folder.json
EasySpider_en/
EasySpider_zh/
TempUserDataFolder/
+124 -1
View File
@@ -36,6 +36,64 @@ let config_context = JSON.parse(
fs.readFileSync(path.join(task_server.getDir(), `config.json`), "utf8")
); //仅在当前进程中使用,不会写入文件
const macOSCommandFolderConfigPath = path.join(
task_server.getDir(),
"macos_command_folder.json"
);
function getMacOSCommandLauncher(folder) {
if (!folder) {
return "";
}
const absoluteFolder = path.resolve(folder);
const appPath = path.join(absoluteFolder, "EasySpider.app");
const scriptPath = path.join(absoluteFolder, "execute_macos.sh");
const executablePath = path.join(absoluteFolder, "easyspider_executestage");
if (!fs.existsSync(appPath) || !fs.existsSync(executablePath)) {
return "";
}
// New packages include the wrapper so it can resolve its own directory.
// Retain compatibility with older portable releases that only contain
// the execution-stage binary.
return fs.existsSync(scriptPath) ? scriptPath : executablePath;
}
function readSavedMacOSCommandFolder() {
try {
const saved = JSON.parse(
fs.readFileSync(macOSCommandFolderConfigPath, "utf8")
);
return getMacOSCommandLauncher(saved.folder) ? path.resolve(saved.folder) : "";
} catch {
return "";
}
}
function detectMacOSCommandFolder() {
const savedFolder = readSavedMacOSCommandFolder();
if (savedFolder) {
return savedFolder;
}
if (process.platform !== "darwin" || !app.isPackaged) {
return "";
}
// This resolves to the directory containing EasySpider.app during a
// normal launch. Under App Translocation it resolves to .../d, which
// does not contain the portable executor and therefore fails validation.
const appBundlePath = path.resolve(process.resourcesPath, "../..");
const candidate = path.dirname(appBundlePath);
return getMacOSCommandLauncher(candidate) ? candidate : "";
}
function saveMacOSCommandFolder(folder) {
const absoluteFolder = path.resolve(folder);
fs.writeFileSync(
macOSCommandFolderConfigPath,
JSON.stringify({folder: absoluteFolder}, null, 2)
);
return absoluteFolder;
}
if (config.debug) {
let logPath = "info.log";
let logFile = fs.createWriteStream(logPath, {flags: "a"});
@@ -1126,7 +1184,19 @@ async function beginInvoke(msg, ws) {
ws.send(
JSON.stringify({
config_folder: task_server.getDir() + "/",
easyspider_location: task_server.getEasySpiderLocation(),
// Never expose an App Translocation directory as a runnable
// command-line location. macOS uses a validated portable
// release folder instead.
easyspider_location:
process.platform === "darwin"
? detectMacOSCommandFolder()
: task_server.getEasySpiderLocation(),
macos_command_folder:
process.platform === "darwin" ? detectMacOSCommandFolder() : "",
macos_command_launcher:
process.platform === "darwin"
? getMacOSCommandLauncher(detectMacOSCommandFolder())
: "",
execution_started,
})
);
@@ -1155,6 +1225,59 @@ async function beginInvoke(msg, ws) {
} catch {
console.log("Cannot get Cookies");
}
} else if (msg.type == 8) {
if (process.platform !== "darwin") {
ws.send(JSON.stringify({
response_type: "macos_command_folder",
canceled: true,
error: "The portable execution folder is only used on macOS.",
}));
return;
}
const currentFolder = detectMacOSCommandFolder();
const parentWindow =
invoke_window && !invoke_window.isDestroyed()
? invoke_window
: mainWindow;
const selection = await dialog.showOpenDialog(parentWindow, {
title:
language === "zh"
? "选择 EasySpider_MacOS 发布文件夹"
: "Select the EasySpider_MacOS release folder",
defaultPath: currentFolder || app.getPath("downloads"),
properties: ["openDirectory"],
message:
language === "zh"
? "请选择同时包含 EasySpider.app 和 easyspider_executestage 的文件夹"
: "Select the folder containing EasySpider.app and easyspider_executestage",
});
if (selection.canceled || selection.filePaths.length === 0) {
ws.send(JSON.stringify({
response_type: "macos_command_folder",
canceled: true,
}));
return;
}
const selectedFolder = path.resolve(selection.filePaths[0]);
const launcher = getMacOSCommandLauncher(selectedFolder);
if (!launcher) {
ws.send(JSON.stringify({
response_type: "macos_command_folder",
canceled: false,
error:
language === "zh"
? "所选文件夹必须同时包含 EasySpider.app 和 easyspider_executestage。"
: "The selected folder must contain both EasySpider.app and easyspider_executestage.",
}));
return;
}
saveMacOSCommandFolder(selectedFolder);
ws.send(JSON.stringify({
response_type: "macos_command_folder",
canceled: false,
macos_command_folder: selectedFolder,
macos_command_launcher: launcher,
}));
} else if (msg.type == 30) {
send_message_to_browser(
JSON.stringify({
+1
View File
@@ -50,6 +50,7 @@ rm -rf "$STAGE_DIR/Code" "$STAGE_DIR/Sample Tasks" "$STAGE_DIR/Data" \
rm -f "$STAGE_DIR/config.json" "$STAGE_DIR/mysql_config.json"
mkdir -p "$STAGE_DIR/Code" "$STAGE_DIR/Sample Tasks" "$STAGE_DIR/Data" \
"$STAGE_DIR/execution_instances"
cp "$SCRIPT_DIR/execute_macos.sh" "$STAGE_DIR/execute_macos.sh"
cp "$REPO_ROOT/ExecuteStage"/*.py "$STAGE_DIR/Code/"
cp "$REPO_ROOT/ExecuteStage/requirements.txt" "$STAGE_DIR/Code/"
cp "$REPO_ROOT/ExecuteStage/Readme.md" "$STAGE_DIR/Code/"
+77 -11
View File
@@ -92,8 +92,17 @@
<label><a href="https://github.com/NaiboWang/EasySpider/wiki/Argument-Instruction"
target="_blank">{{`Click Here~点击这里` | lang}}</a> {{`Here to see argument instruction.~这里查看参数配置说明。` | lang}}</label>
<label v-if="OS=='MacOS'">{{`对于MacOS系统,EasySpider提供了两个不同的执行程序,分别为easyspider_executestage和easyspider_executestage_full,前者执行时加载速度较快,并提供了除OCR识别和数据去重以外的全部功能;后者则提供了包括OCR识别和数据去重在内的全部功能,但运行时加载速度较慢,需要等待2-10分钟才能执行程序,请根据自己的需求选择执行哪个程序。~For MacOS system, EasySpider provides two different execution programs, 'easyspider_executestage' and 'easyspider_executestage_full', the former loads faster when executing, and provides all functions except OCR recognition and data deduplication; the latter provides all functions including OCR recognition and data deduplication, but the loading speed is slower when running, and it takes 2-10 minutes to wait for the program to execute, please choose which program to execute according to your needs.` | lang}}</label>
<label>{{ `Please open a terminal (For Windows, please use PowerShell instead of CMD), go to EasySpider's folder, and then copy (Command/Ctrl + c) the following command to run the task (EasySpider can quit when executing command for ease of timed execution, and you can set --read_type to "remote" for remote execution):~请在EasySpider目录下打开命令行工具Terminal (Windows请使用PowerShell而不是CMD),然后复制(Command/Ctrl + c)和运行以下命令以执行任务(执行命令时可以退出EasySpider以方便定时执行,如需要远程调用则需要将--read_type设置为remote并设置远程地址):` | lang }}</label>
<textarea class="form-control" style="height:150px">cd {{easyspider_location}}
<label v-if="OS=='MacOS'">{{ `Copy the following command into Terminal. The command uses the real EasySpider_MacOS release folder selected below and does not depend on an App Translocation path. You may quit EasySpider after the execution instance has been generated.~请将以下命令复制到Terminal运行。该命令使用下方选择的真实EasySpider_MacOS发布文件夹,不依赖App Translocation临时路径;生成执行实例后可以退出EasySpider。` | lang }}</label>
<label v-else>{{ `Please open a terminal (For Windows, please use PowerShell instead of CMD), go to EasySpider's folder, and then copy (Command/Ctrl + c) the following command to run the task (EasySpider can quit when executing command for ease of timed execution, and you can set --read_type to "remote" for remote execution):~请在EasySpider目录下打开命令行工具Terminal (Windows请使用PowerShell而不是CMD),然后复制(Command/Ctrl + c)和运行以下命令以执行任务(执行命令时可以退出EasySpider以方便定时执行,如需要远程调用则需要将--read_type设置为remote并设置远程地址):` | lang }}</label>
<div v-if="OS=='MacOS'" style="margin: 8px 0 10px 0;">
<button type="button" class="btn btn-outline-primary btn-sm"
v-on:click="selectMacOSCommandFolder">{{ `Select/change EasySpider_MacOS folder~选择或更改EasySpider_MacOS文件夹` | lang }}</button>
<div style="margin-top: 6px; word-break: break-all;">
{{macos_command_folder}}
</div>
</div>
<textarea v-if="OS=='MacOS'" class="form-control" style="height:170px" readonly>{{macos_command_line}}</textarea>
<textarea v-else class="form-control" style="height:150px">cd {{easyspider_location}}
{{command}} --config_folder "{{config_folder}}" --headless 0 --read_type local --config_file_name config.json --saved_file_name </textarea>
</div>
<!-- <div class="modal-footer">
@@ -349,6 +358,10 @@
easyspider_location: "",
mysql_config_path: "",
executionStarted: false,
macos_command_folder: "",
macos_command_launcher: "",
macos_command_line: "",
showCommandAfterFolderSelection: false,
OS: "Windows",
}, mounted() {
$.get(this.backEndAddressServiceWrapper + "/getConfig", function (result) {
@@ -398,6 +411,16 @@
},
readFromExcel: function () {
$('#excelModal').modal('show');
}, selectMacOSCommandFolder: function () {
this.showCommandAfterFolderSelection = true;
ws.send(JSON.stringify({type: 8}));
}, showMacOSCommandDialog: function () {
changeCommand();
if (this.macos_command_launcher !== "") {
$('#myModal').modal('show');
} else {
this.selectMacOSCommandFolder();
}
}, submitFile: function () {
let form_data = new FormData();
this.file = $('#excelFile').prop('files')[0];
@@ -522,8 +545,12 @@
}
};
ws.send(JSON.stringify(message));
changeCommand();
$('#myModal').modal('show');
if (detectOperatingSystemAndArch().OS == 'MacOS') {
this.showMacOSCommandDialog();
} else {
changeCommand();
$('#myModal').modal('show');
}
// }
},
remoteExecute: function () {
@@ -572,9 +599,9 @@
// command dialog used by older packages.
setTimeout(function () {
if (!app.$data.executionStarted) {
$('#myModal').modal('show');
app.showMacOSCommandDialog();
}
}, 250);
}, 1000);
}
// });
});
@@ -597,16 +624,29 @@
} else if (systemInfo.OS == 'Linux') {
app.$data.command = "./EasySpider/resources/app/chrome_linux64/easyspider_executestage --ids '[" + app.$data.ID.toString() + "]' --user_data " + (app.$data.with_user_data ? "1" : "0") + " --server_address " + app.$data.backEndAddressServiceWrapper;
} else if (systemInfo.OS == 'MacOS') {
if (getUrlParam("lang") == "zh") {
app.$data.easyspider_location = "你的EasySpider文件夹,如:cd /Users/" + app.$data.config_folder.split("/")[2] + "/Downloads/EasySpider_MacOS";
if (app.$data.macos_command_launcher !== "") {
app.$data.macos_command_line =
"cd " + quotePOSIXShell(app.$data.macos_command_folder) + "\n" +
quotePOSIXShell(app.$data.macos_command_launcher) +
" --ids " + quotePOSIXShell("[" + app.$data.ID.toString() + "]") +
" --user_data " + (app.$data.with_user_data ? "1" : "0") +
" --server_address " + quotePOSIXShell(app.$data.backEndAddressServiceWrapper) +
" --config_folder " + quotePOSIXShell(app.$data.config_folder) +
" --headless 0 --read_type local" +
" --config_file_name config.json --saved_file_name ''";
} else {
app.$data.easyspider_location = "Your EasySpider folder, such as: cd /Users/" + app.$data.config_folder.split("/")[2] + "/Downloads/EasySpider_MacOS";
app.$data.macos_command_line = getUrlParam("lang") == "zh"
? "请先选择包含 EasySpider.app 和 easyspider_executestage 的 EasySpider_MacOS 文件夹。"
: "Select the EasySpider_MacOS folder containing EasySpider.app and easyspider_executestage first.";
}
app.$data.command = "./easyspider_executestage --ids '[" + app.$data.ID.toString() + "]' --user_data " + (app.$data.with_user_data ? "1" : "0") + " --server_address " + app.$data.backEndAddressServiceWrapper;
}
// });
}
function quotePOSIXShell(value) {
return "'" + String(value).replaceAll("'", "'\"'\"'") + "'";
}
$.get(app.$data.backEndAddressServiceWrapper + "/queryTask?id=" + sId, function (result) {
app.$data.task = result;
app.$data.show = true;
@@ -636,12 +676,38 @@
};
ws.onmessage = function (message) {
message = JSON.parse(message.data);
if (message.response_type === "macos_command_folder") {
if (message.error) {
alert(message.error);
app.$data.showCommandAfterFolderSelection = false;
return;
}
if (message.canceled) {
app.$data.showCommandAfterFolderSelection = false;
return;
}
app.$data.macos_command_folder = message.macos_command_folder;
app.$data.macos_command_launcher = message.macos_command_launcher;
changeCommand();
if (app.$data.showCommandAfterFolderSelection) {
app.$data.showCommandAfterFolderSelection = false;
$('#myModal').modal('show');
}
return;
}
if (message.config_folder) {
app.$data.config_folder = message.config_folder.replaceAll("\\", "/");
}
if (message.easyspider_location) {
if (message.macos_command_folder) {
app.$data.macos_command_folder = message.macos_command_folder;
}
if (message.macos_command_launcher) {
app.$data.macos_command_launcher = message.macos_command_launcher;
}
if (message.easyspider_location && detectOperatingSystemAndArch().OS != 'MacOS') {
app.$data.easyspider_location = message.easyspider_location.replace("/EasySpider.app/", "");
}
changeCommand();
app.$data.executionStarted = message.execution_started === true;
}
ws.onclose = function () {