Skip Loop

This commit is contained in:
naibo 2023-12-19 10:11:42 +08:00
parent d81efd4454
commit 69f0c1128c
11 changed files with 66 additions and 23 deletions

View File

@ -1 +1 @@
{"webserver_address":"http://localhost","webserver_port":8074,"user_data_folder":"","debug":false,"copyright":1,"sys_version":"x64","mysql_config_path":"./mysql_config.json","absolute_user_data_folder":"/Users/naibo/Documents/EasySpider/ElectronJS/user_data"} {"webserver_address":"http://localhost","webserver_port":8074,"user_data_folder":"./user_data","debug":false,"copyright":1,"sys_version":"x64","mysql_config_path":"./mysql_config.json","absolute_user_data_folder":"D:\\Documents\\Projects\\EasySpider\\ElectronJS\\user_data"}

View File

@ -15,6 +15,7 @@ const util = require('util');
let config = fs.readFileSync(path.join(task_server.getDir(), `config.json`), 'utf8'); let config = fs.readFileSync(path.join(task_server.getDir(), `config.json`), 'utf8');
config = JSON.parse(config); config = JSON.parse(config);
let config_context = JSON.parse(fs.readFileSync(path.join(task_server.getDir(), `config.json`), 'utf8')); //仅在当前进程中使用,不会写入文件
if (config.debug) { if (config.debug) {
let logPath = 'info.log' let logPath = 'info.log'
@ -302,7 +303,7 @@ async function beginInvoke(msg, ws) {
// It will prompt an accessibility permission request dialog, if needed. // It will prompt an accessibility permission request dialog, if needed.
if (process.platform != "linux" && process.platform != "darwin") { if (process.platform != "linux" && process.platform != "darwin") {
// 非用户信息模式下,设置窗口位置 // 非用户信息模式下,设置窗口位置
if (config.user_data_folder == null || config.user_data_folder == undefined || config.user_data_folder == "") { if (config_context.user_data_folder == null || config_context.user_data_folder == undefined || config_context.user_data_folder == "") {
const {windowManager} = require("node-window-manager"); const {windowManager} = require("node-window-manager");
const window = windowManager.getActiveWindow(); const window = windowManager.getActiveWindow();
console.log(window); console.log(window);
@ -815,6 +816,7 @@ function send_message_to_browser(message) {
const WebSocket = require('ws'); const WebSocket = require('ws');
const {all} = require("express/lib/application"); const {all} = require("express/lib/application");
const {copy} = require("selenium-webdriver/io");
let wss = new WebSocket.Server({port: websocket_port}); let wss = new WebSocket.Server({port: websocket_port});
wss.on('connection', function (ws) { wss.on('connection', function (ws) {
ws.on('message', async function (message, isBinary) { ws.on('message', async function (message, isBinary) {
@ -922,9 +924,10 @@ async function runBrowser(lang = "en", user_data_folder = '', mobile = false) {
console.log(dir); console.log(dir);
options.addArguments("--user-data-dir=" + dir); options.addArguments("--user-data-dir=" + dir);
config.user_data_folder = user_data_folder; config.user_data_folder = user_data_folder;
config_context.user_data_folder = user_data_folder;
fs.writeFileSync(path.join(task_server.getDir(), "config.json"), JSON.stringify(config)); fs.writeFileSync(path.join(task_server.getDir(), "config.json"), JSON.stringify(config));
} else { } else {
config.user_data_folder = ""; config_context.user_data_folder = "";
} }
if (mobile) { if (mobile) {
const mobileEmulation = { const mobileEmulation = {

View File

@ -368,9 +368,9 @@ exports.start = function(port = 8074) {
res.write(eid.toString(), 'utf8'); res.write(eid.toString(), 'utf8');
res.end(); res.end();
} else if(pathName == "/getConfig"){ } else if(pathName == "/getConfig"){
let config = fs.readFileSync(path.join(getDir(), `config.json`), 'utf8'); let config_file = fs.readFileSync(path.join(getDir(), `config.json`), 'utf8');
config = JSON.parse(config); config_file = JSON.parse(config_file);
res.write(JSON.stringify(config)); res.write(JSON.stringify(config_file));
res.end(); res.end();
} else if(pathName == "/setUserDataFolder"){ } else if(pathName == "/setUserDataFolder"){
let config = fs.readFileSync(path.join(getDir(), `config.json`), 'utf8'); let config = fs.readFileSync(path.join(getDir(), `config.json`), 'utf8');

View File

@ -29,9 +29,7 @@
<ol class="breadcrumb" style="padding-left:23px;padding-bottom: 0;margin-bottom:0;background-color: white"> <ol class="breadcrumb" style="padding-left:23px;padding-bottom: 0;margin-bottom:0;background-color: white">
<li @click="gotoHome" class="breadcrumb-item"><a href="#">{{"Home~首页" | lang}}</a></li> <li @click="gotoHome" class="breadcrumb-item"><a href="#">{{"Home~首页" | lang}}</a></li>
<li @click="gotoInfo" aria-current="page" class="breadcrumb-item" style="color: black"><a href="#">{{"Task Information~任务信息" | lang}}</a></li> <li @click="gotoInfo" aria-current="page" class="breadcrumb-item" style="color: black"><a href="#">{{"Task Information~任务信息" | lang}}</a></li>
<li aria-current="page" class="breadcrumb-item active" style="color: black">{{"Task Modification~任务流程修改" <li aria-current="page" class="breadcrumb-item active" style="color: black">{{"Task Flow~任务流程" | lang}}</li>
| lang}}
</li>
</ol> </ol>
</nav> </nav>
</div> </div>
@ -586,6 +584,12 @@ If the expression returns a value greater than 0 or evaluates to True, the loop
</div> </div>
</div> </div>
</div> </div>
<div v-if="parseInt(loopType) < 5 && parseInt(loopType) > 0">
<label>Skip the first few loops (enter 2 to skip the first 2 loops):</label>
<input spellcheck="false" onkeydown="inputDelete(event)" required class="form-control" type="number" v-model.number='nowNode["parameters"]["skipCount"]'></input>
</div>
<label>Waiting time in seconds after a history record rollback: </label> <label>Waiting time in seconds after a history record rollback: </label>
<input spellcheck=false onkeydown="inputDelete(event)" required type="number" class="form-control" v-model.number='list.nl[index.nowNodeIndex]["parameters"]["historyWait"]'></input> <input spellcheck=false onkeydown="inputDelete(event)" required type="number" class="form-control" v-model.number='list.nl[index.nowNodeIndex]["parameters"]["historyWait"]'></input>
<label>After executed, whether scroll down:</label> <label>After executed, whether scroll down:</label>

View File

@ -29,9 +29,7 @@
<ol class="breadcrumb" style="padding-left:23px;padding-bottom: 0;margin-bottom:0;background-color: white"> <ol class="breadcrumb" style="padding-left:23px;padding-bottom: 0;margin-bottom:0;background-color: white">
<li @click="gotoHome" class="breadcrumb-item"><a href="#">{{"Home~首页" | lang}}</a></li> <li @click="gotoHome" class="breadcrumb-item"><a href="#">{{"Home~首页" | lang}}</a></li>
<li @click="gotoInfo" aria-current="page" class="breadcrumb-item" style="color: black"><a href="#">{{"Task Information~任务信息" | lang}}</a></li> <li @click="gotoInfo" aria-current="page" class="breadcrumb-item" style="color: black"><a href="#">{{"Task Information~任务信息" | lang}}</a></li>
<li aria-current="page" class="breadcrumb-item active" style="color: black">{{"Task Modification~任务流程修改" <li aria-current="page" class="breadcrumb-item active" style="color: black">{{"Task Flow~任务流程" | lang}}</li>
| lang}}
</li>
</ol> </ol>
</nav> </nav>
</div> </div>
@ -146,7 +144,7 @@
<p><input spellcheck=false onkeydown="inputDelete(event)" type="checkbox" v-model='useLoop'></input>使用相对循环内的XPath定位到的元素</p> <p><input spellcheck=false onkeydown="inputDelete(event)" type="checkbox" v-model='useLoop'></input>使用相对循环内的XPath定位到的元素</p>
</div> </div>
<div> <div>
<label>XPath或者用point(10,10)表示点击网页坐标位置(10, 10)以用来点击空白区域推出弹窗对话框等): <span style="font-size: 30px!important;" title="相对XPATH写法:以/开头如循环项XPATH为/html/body/div[1],您的输入为/*[@id='tab-customer'],则最终寻址的xpath为/html/body/div[1]/*[@id='tab-customer']"></span></label> <label>XPath或者用point(10,10)表示点击网页坐标位置(10, 10)以用来点击空白区域推出弹窗对话框文本列表等): <span style="font-size: 30px!important;" title="相对XPATH写法:以/开头如循环项XPATH为/html/body/div[1],您的输入为/*[@id='tab-customer'],则最终寻址的xpath为/html/body/div[1]/*[@id='tab-customer']"></span></label>
<textarea spellcheck=false onkeydown="inputDelete(event)" class="form-control" rows="2" v-model='nowNode["parameters"]["xpath"]'></textarea> <textarea spellcheck=false onkeydown="inputDelete(event)" class="form-control" rows="2" v-model='nowNode["parameters"]["xpath"]'></textarea>
<p><button type="button" data-toggle="modal" data-target="#myModal_XPath" @click="changeXPaths(nowNode['parameters']['allXPaths'])" class="btn btn-primary" style="margin-top: 10px">点此查看其他等价的XPath</button></p> <p><button type="button" data-toggle="modal" data-target="#myModal_XPath" @click="changeXPaths(nowNode['parameters']['allXPaths'])" class="btn btn-primary" style="margin-top: 10px">点此查看其他等价的XPath</button></p>
</div> </div>
@ -586,8 +584,15 @@ print(emotlib.emoji()) # 使用其中的函数。
</div> </div>
</div> </div>
</div> </div>
<div v-if="parseInt(loopType) < 5 && parseInt(loopType) > 0">
<label>跳过前几次循环如要跳过前2个循环则填写2</label>
<input spellcheck=false onkeydown="inputDelete(event)" required class="form-control" type="number" v-model.number='nowNode["parameters"]["skipCount"]'></input>
</div>
<label><b>历史记录回退后</b>等待秒数:</label> <label><b>历史记录回退后</b>等待秒数:</label>
<input spellcheck=false onkeydown="inputDelete(event)" required type="number" class="form-control" v-model.number='list.nl[index.nowNodeIndex]["parameters"]["historyWait"]'></input> <input spellcheck=false onkeydown="inputDelete(event)" required type="number" class="form-control" v-model.number='list.nl[index.nowNodeIndex]["parameters"]["historyWait"]'></input>
<label>执行完是否向下滚动:</label> <label>执行完是否向下滚动:</label>
<select v-model='nowNode["parameters"]["scrollType"]' class="form-control"> <select v-model='nowNode["parameters"]["scrollType"]' class="form-control">
<option :value = 0>不滚动</option> <option :value = 0>不滚动</option>

View File

@ -316,6 +316,7 @@ function addParameters(t) {
t["parameters"]["breakMode"] = 0; //break类型0代表JS2代表系统命令 t["parameters"]["breakMode"] = 0; //break类型0代表JS2代表系统命令
t["parameters"]["breakCode"] = ""; //break条件 t["parameters"]["breakCode"] = ""; //break条件
t["parameters"]["breakCodeWaitTime"] = 0; //break条件等待时间 t["parameters"]["breakCodeWaitTime"] = 0; //break条件等待时间
t["parameters"]["skipCount"] = 0; //跳过前多少次循环
} else if (t.option == 9) { //条件 } else if (t.option == 9) { //条件
t["title"] = LANG("判断条件 - 从左往右依次判断", "Judgment Condition - Judge from Left to Right"); t["title"] = LANG("判断条件 - 从左往右依次判断", "Judgment Condition - Judge from Left to Right");
} else if (t.option == 10) { //条件分支 } else if (t.option == 10) { //条件分支

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
{"id":298,"name":"京东全球版-专业的综合网上购物商城","url":"https://www.jd.com","links":"about:blank","create_time":"12/19/2023, 9:55:11 AM","update_time":"12/19/2023, 10:00:26 AM","version":"0.6.0","saveThreshold":10,"quitWaitTime":60,"environment":0,"maximizeWindow":0,"maxViewLength":15,"recordLog":1,"outputFormat":"xlsx","saveName":"current_time","dataWriteMode":1,"inputExcel":"","startFromExit":0,"pauseKey":"p","containJudge":false,"browser":"chrome","desc":"https://www.jd.com","inputParameters":[{"id":0,"name":"loopText_0","nodeId":2,"nodeName":"循环 - 网址列表","desc":"要输入的文本/网址,多行以\\n分开","type":"text","exampleValue":"https://www.jd.com?id=1\nhttps://www.jd.com?id=2\nhttps://www.jd.com?id=3\nhttps://www.jd.com?id=4\nhttps://www.jd.com?id=5","value":"https://www.jd.com?id=1\nhttps://www.jd.com?id=2\nhttps://www.jd.com?id=3\nhttps://www.jd.com?id=4\nhttps://www.jd.com?id=5"}],"outputParameters":[],"graph":[{"index":0,"id":0,"parentId":0,"type":-1,"option":0,"title":"root","sequence":[2],"parameters":{"history":1,"tabIndex":0,"useLoop":false,"xpath":"","iframe":false,"wait":0,"waitType":0,"beforeJS":"","beforeJSWaitTime":0,"afterJS":"","afterJSWaitTime":0,"waitElement":"","waitElementTime":10,"waitElementIframeIndex":0},"isInLoop":false},{"id":2,"index":1,"parentId":1,"type":0,"option":1,"title":"打开网页","sequence":[],"isInLoop":true,"position":0,"parameters":{"useLoop":true,"xpath":"","wait":0,"waitType":0,"beforeJS":"","beforeJSWaitTime":0,"afterJS":"","afterJSWaitTime":0,"waitElement":"","waitElementTime":10,"waitElementIframeIndex":0,"url":"https://www.jd.com","links":"https://www.jd.com","maxWaitTime":10,"scrollType":0,"scrollCount":1,"scrollWaitTime":1,"cookies":""}},{"id":1,"index":2,"parentId":0,"type":1,"option":8,"title":"循环 - 网址列表","sequence":[1,3],"isInLoop":false,"position":0,"parameters":{"history":4,"tabIndex":-1,"useLoop":false,"xpath":"//*[@id=\"key\"]","iframe":false,"wait":0,"waitType":0,"beforeJS":"","beforeJSWaitTime":0,"afterJS":"","afterJSWaitTime":0,"waitElement":"","waitElementTime":10,"waitElementIframeIndex":0,"scrollType":0,"scrollCount":1,"scrollWaitTime":1,"loopType":4,"pathList":"","textList":"https://www.jd.com?id=1\nhttps://www.jd.com?id=2\nhttps://www.jd.com?id=3\nhttps://www.jd.com?id=4\nhttps://www.jd.com?id=5","code":"","waitTime":0,"exitCount":0,"exitElement":"//body","historyWait":2,"breakMode":0,"breakCode":"","breakCodeWaitTime":0,"skipCount":0,"allXPaths":["/html/body/div[4]/div[1]/div[2]/div[1]/input[1]","//input[contains(., '')]","id(\"key\")","//INPUT[@class='text']","/html/body/div[last()-6]/div/div[last()-2]/div/input"]}},{"id":3,"index":3,"parentId":1,"type":0,"option":4,"title":"输入文字","sequence":[],"isInLoop":true,"position":1,"parameters":{"history":4,"tabIndex":-1,"useLoop":true,"xpath":"//*[@id=\"key\"]","iframe":false,"wait":0,"waitType":0,"beforeJS":"","beforeJSWaitTime":0,"afterJS":"","afterJSWaitTime":0,"waitElement":"","waitElementTime":10,"waitElementIframeIndex":0,"value":"123","index":0,"allXPaths":["/html/body/div[4]/div[1]/div[2]/div[1]/input[1]","//input[contains(., '')]","id(\"key\")","//INPUT[@class='text']","/html/body/div[last()-6]/div/div[last()-2]/div/input"]}}]}

View File

@ -12,7 +12,7 @@
"justMyCode": false, "justMyCode": false,
// "args": ["--ids", "[7]", "--read_type", "remote", "--headless", "0"] // "args": ["--ids", "[7]", "--read_type", "remote", "--headless", "0"]
// "args": ["--ids", "[9]", "--read_type", "remote", "--headless", "0", "--saved_file_name", "YOUTUBE"] // "args": ["--ids", "[9]", "--read_type", "remote", "--headless", "0", "--saved_file_name", "YOUTUBE"]
"args": ["--ids", "[62]", "--headless", "0", "--user_data", "0", "--keyboard", "0", "args": ["--ids", "[77]", "--headless", "0", "--user_data", "0", "--keyboard", "0",
"--read_type", "remote"] "--read_type", "remote"]
// "args": "--ids '[97]' --user_data 1 --server_address http://localhost:8074 --config_folder '/Users/naibo/Documents/EasySpider/ElectronJS/' --headless 0 --read_type remote --config_file_name config.json --saved_file_name" // "args": "--ids '[97]' --user_data 1 --server_address http://localhost:8074 --config_folder '/Users/naibo/Documents/EasySpider/ElectronJS/' --headless 0 --read_type remote --config_file_name config.json --saved_file_name"
} }

View File

@ -333,6 +333,10 @@ class BrowserThread(Thread):
except: except:
node["parameters"]["exitElement"] = "//body" node["parameters"]["exitElement"] = "//body"
node["parameters"]["quickExtractable"] = False # 是否可以快速提取 node["parameters"]["quickExtractable"] = False # 是否可以快速提取
try:
skipCount = node["parameters"]["skipCount"]
except:
node["parameters"]["skipCount"] = 0
# 如果(不)固定元素列表循环中只有一个提取数据操作,且提取数据操作的提取内容为元素截图,那么可以快速提取 # 如果(不)固定元素列表循环中只有一个提取数据操作,且提取数据操作的提取内容为元素截图,那么可以快速提取
if len(node["sequence"]) == 1 and self.procedure[node["sequence"][0]]["option"] == 3 and (int(node["parameters"]["loopType"]) == 1 or int(node["parameters"]["loopType"]) == 2): if len(node["sequence"]) == 1 and self.procedure[node["sequence"][0]]["option"] == 3 and (int(node["parameters"]["loopType"]) == 1 or int(node["parameters"]["loopType"]) == 2):
try: try:
@ -347,6 +351,8 @@ class BrowserThread(Thread):
node["parameters"]["quickExtractable"] = False # 如果是iframe那么不可以快速提取 node["parameters"]["quickExtractable"] = False # 如果是iframe那么不可以快速提取
else: else:
node["parameters"]["quickExtractable"] = True # 先假设可以快速提取 node["parameters"]["quickExtractable"] = True # 先假设可以快速提取
if node["parameters"]["skipCount"] > 0:
node["parameters"]["quickExtractable"] = False # 如果有跳过的元素,那么不可以快速提取
for param in params: for param in params:
optimizable = detect_optimizable(param, ignoreWaitElement=False, waitElement=waitElement) optimizable = detect_optimizable(param, ignoreWaitElement=False, waitElement=waitElement)
try: try:
@ -1210,7 +1216,13 @@ class BrowserThread(Thread):
xpath) xpath)
self.print_and_log("找不到循环元素:", xpath) self.print_and_log("找不到循环元素:", xpath)
index = 0 index = 0
skipCount = node["parameters"]["skipCount"]
while index < len(elements): while index < len(elements):
if index < skipCount:
index += 1
self.print_and_log("跳过第" + str(index) + "个元素")
self.print_and_log("Skip the " + str(index) + "th element")
continue
try: try:
element = elements[index] element = elements[index]
element_text = element.text element_text = element.text
@ -1266,7 +1278,13 @@ class BrowserThread(Thread):
paths = node["parameters"]["pathList"].split("\n") paths = node["parameters"]["pathList"].split("\n")
# for path in node["parameters"]["pathList"].split("\n"): # for path in node["parameters"]["pathList"].split("\n"):
index = 0 index = 0
skipCount = node["parameters"]["skipCount"]
while index < len(paths): while index < len(paths):
if index < skipCount:
index += 1
self.print_and_log("跳过第" + str(index) + "个元素")
self.print_and_log("Skip the " + str(index) + "th element")
continue
path = paths[index] path = paths[index]
try: try:
path = replace_field_values( path = replace_field_values(
@ -1322,7 +1340,14 @@ class BrowserThread(Thread):
if len(textList) == 1: # 如果固定文本列表只有一行,现在就可以替换变量 if len(textList) == 1: # 如果固定文本列表只有一行,现在就可以替换变量
textList = replace_field_values( textList = replace_field_values(
node["parameters"]["textList"], self.outputParameters, self).split("\n") node["parameters"]["textList"], self.outputParameters, self).split("\n")
skipCount = node["parameters"]["skipCount"]
index = 0
for text in textList: for text in textList:
if index < skipCount:
index += 1
self.print_and_log("跳过第" + str(index) + "个文本")
self.print_and_log("Skip the " + str(index) + "th text")
continue
text = replace_field_values(text, self.outputParameters, self) text = replace_field_values(text, self.outputParameters, self)
# self.recordLog("当前循环文本|Current loop text:", text) # self.recordLog("当前循环文本|Current loop text:", text)
for i in node["sequence"]: # 挨个执行操作 for i in node["sequence"]: # 挨个执行操作
@ -1348,11 +1373,14 @@ class BrowserThread(Thread):
if len(urlList) == 1: # 如果固定网址列表只有一行,现在就可以替换变量 if len(urlList) == 1: # 如果固定网址列表只有一行,现在就可以替换变量
urlList = replace_field_values( urlList = replace_field_values(
node["parameters"]["textList"], self.outputParameters, self).split("\n") node["parameters"]["textList"], self.outputParameters, self).split("\n")
# urlList = [] skipCount = node["parameters"]["skipCount"]
# for url in tempList: index = 0
# if url != "":
# urlList.append(url)
for url in urlList: for url in urlList:
if index < skipCount:
index += 1
self.print_and_log("跳过第" + str(index) + "个网址")
self.print_and_log("Skip the " + str(index) + "th url")
continue
url = replace_field_values(url, self.outputParameters, self) url = replace_field_values(url, self.outputParameters, self)
# self.recordLog("当前循环网址|Current loop url:", url) # self.recordLog("当前循环网址|Current loop url:", url)
for i in node["sequence"]: for i in node["sequence"]:
@ -1400,7 +1428,7 @@ class BrowserThread(Thread):
self.history["handle"] = self.browser.current_window_handle self.history["handle"] = self.browser.current_window_handle
self.scrollDown(node["parameters"]) self.scrollDown(node["parameters"])
# 打开网页事件 # 打开网页操作
def openPage(self, param, loopValue): def openPage(self, param, loopValue):
time.sleep(1) # 打开网页后强行等待至少1秒 time.sleep(1) # 打开网页后强行等待至少1秒
if len(self.browser.window_handles) > 1: if len(self.browser.window_handles) > 1:
@ -1465,7 +1493,7 @@ class BrowserThread(Thread):
self.history["index"] = 0 self.history["index"] = 0
self.scrollDown(param) # 控制屏幕向下滚动 self.scrollDown(param) # 控制屏幕向下滚动
# 键盘输入事件 # 键盘输入操作
def inputInfo(self, param, loopValue): def inputInfo(self, param, loopValue):
time.sleep(0.1) # 输入之前等待0.1秒 time.sleep(0.1) # 输入之前等待0.1秒
try: try:
@ -1517,7 +1545,7 @@ class BrowserThread(Thread):
xpath + ", please try to set the wait time before executing this operation") xpath + ", please try to set the wait time before executing this operation")
self.print_and_log("找不到输入框元素:" + xpath + ",请尝试在执行此操作前设置等待时间") self.print_and_log("找不到输入框元素:" + xpath + ",请尝试在执行此操作前设置等待时间")
# 点击元素事件 # 点击元素操作
def clickElement(self, param, loopElement=None, clickPath="", index=0): def clickElement(self, param, loopElement=None, clickPath="", index=0):
try: try:
maxWaitTime = int(param["maxWaitTime"]) maxWaitTime = int(param["maxWaitTime"])
@ -1853,7 +1881,7 @@ class BrowserThread(Thread):
self.outputParameters[key] = "" self.outputParameters[key] = ""
self.recordLog("清空输出参数|Clear output parameters") self.recordLog("清空输出参数|Clear output parameters")
# 提取数据事件 # 提取数据操作
def getData(self, param, loopElement, isInLoop=True, parentPath="", index=0): def getData(self, param, loopElement, isInLoop=True, parentPath="", index=0):
parentPath = replace_field_values( parentPath = replace_field_values(
parentPath, self.outputParameters, self) parentPath, self.outputParameters, self)