mirror of
https://github.com/NaiboWang/EasySpider.git
synced 2025-04-16 16:26:56 +08:00
V0.6.0 Pre
This commit is contained in:
parent
82dac5ad00
commit
cffaeeb6fa
@ -61,7 +61,7 @@ B站最新版特性视频已上传,新视频非常有用,推荐大家观看
|
||||
注意,v0.3.1版本任务task文件夹内`.json`文件和之前所有版本均不兼容,请重新设计v0.3.1版本任务。
|
||||
|
||||
## 更新说明
|
||||
1. 高级操作:
|
||||
1. 自定义操作:
|
||||
- 可以在任务流程中**执行自定义脚本**,包括在浏览器中**执行Javascript指令**以及**操作系统级别的脚本调用**并可**得到命令返回值并记录**,大大扩展了可操作空间。
|
||||
|
||||

|
||||
|
@ -61,7 +61,7 @@ B站最新版特性视频已上传,新视频非常有用,推荐大家观看
|
||||
注意,v0.3.1版本任务task文件夹内`.json`文件和之前所有版本均不兼容,请重新设计v0.3.1版本任务。
|
||||
|
||||
## 更新说明
|
||||
1. 高级操作:
|
||||
1. 自定义操作:
|
||||
- 可以在任务流程中**执行自定义脚本**,包括在浏览器中**执行Javascript指令**以及**操作系统级别的脚本调用**并可**得到命令返回值并记录**,大大扩展了可操作空间。
|
||||
|
||||

|
||||
|
@ -58,7 +58,7 @@ B站最新版特性视频已上传,新视频非常有用,推荐大家观看
|
||||
注意,v0.3.1版本任务task文件夹内`.json`文件和之前所有版本均不兼容,请重新设计v0.3.1版本任务。
|
||||
|
||||
## 更新说明
|
||||
1. 高级操作:
|
||||
1. 自定义操作:
|
||||
- 可以在任务流程中**执行自定义脚本**,包括在浏览器中**执行Javascript指令**以及**操作系统级别的脚本调用**并可**得到命令返回值并记录**,大大扩展了可操作空间。
|
||||
|
||||

|
||||
|
@ -60,7 +60,7 @@ B站最新版特性视频已上传,新视频非常有用,推荐大家观看
|
||||
注意,v0.3.1版本任务task文件夹内`.json`文件和之前所有版本均不兼容,请重新设计v0.3.1版本任务。
|
||||
|
||||
## 更新说明
|
||||
1. 高级操作:
|
||||
1. 自定义操作:
|
||||
- 可以在任务流程中**执行自定义脚本**,包括在浏览器中**执行Javascript指令**以及**操作系统级别的脚本调用**并可**得到命令返回值并记录**,大大扩展了可操作空间。
|
||||
|
||||

|
||||
|
Binary file not shown.
Binary file not shown.
@ -59,7 +59,7 @@
|
||||
<h4 style="margin-top: 20px">Copyright and Disclaimer</h4>
|
||||
<p>Please carefully read the following instructions regarding the use of the software and commercial payments. If you agree, please accept the agreement.</p>
|
||||
<textarea class="form-control" style="margin:0 auto;width:90%; color:black; height: 450px; min-height: 200px; background: white" readonly>
|
||||
This software is intended for educational and communication purposes only. It is strictly prohibited to use the software for any illegal activities or operations, such as crawling government/military websites that are not allowed to be crawled. The user bears all consequences resulting from the use of this software and the author shall not be held responsible or liable in any way. Furthermore, the software is protected by patent rights. If you intend to use it for commercial purposes or profit-making activities, such as using the software for client orders, selling the collected data, please contact Hangzhou Tianqin Intellectual Property Agency Co., Ltd. (http://www.tqip.com/) for patent authorization and payment operations: https://www.patentguru.com/cn/search?q=一种自定义提取流程的服务封装系统
|
||||
This software is intended for educational and communication purposes only. It is strictly prohibited to use the software for any illegal activities or operations, such as crawling government/military websites that are not allowed to be crawled. The user bears all consequences resulting from the use of this software and the author shall not be held responsible or liable in any way. Furthermore, the software is protected by patent rights. If you intend to use it for commercial purposes or profit-making activities, such as using the software for client orders, selling the collected data, please contact author: naibowang@foxmail.com for patent authorization and payment operations: https://www.patentguru.com/cn/search?q=一种自定义提取流程的服务封装系统
|
||||
For individual users, EasySpider is a completely free and ad-free open-source software. The development and maintenance of the software rely solely on the author's voluntary efforts. Therefore, you can choose to support the author, allowing them to have more enthusiasm and energy to maintain this software. Alternatively, if you have profited from using this software, you are welcome to support the author through the following methods:
|
||||
|
||||
1. PayPal account: naibowang, or scan the QR code provided in the software package.
|
||||
@ -142,9 +142,9 @@ For individual users, EasySpider is a completely free and ad-free open-source so
|
||||
<div v-else-if="lang=='zh'">
|
||||
<div v-if="step == -1">
|
||||
<h4 style="margin-top: 20px">版权声明和注意事项</h4>
|
||||
<p>请仔细阅读下方有关软件使用和商用付费的说明,同意请接受协议。</p>
|
||||
<p>请接受下方使用协议以使用软件,不同意请退出。</p>
|
||||
<textarea class="form-control" style="margin:0 auto;width:90%; color:black; height: 480px; min-height: 200px; background: white" readonly>
|
||||
本软件仅供学习交流使用,严禁使用软件进行任何违法违规的操作,如爬取不允许爬取的政府/军事机关网站等。使用本软件所造成的一切后果由使用者自负,与作者本人无关,作者不会承担任何责任。同时,软件受到专利权保护,如要用于商业用途,如使用软件进行盈利接单,出售采集到的数据等,请联系杭州天勤知识产权代理有限公司(http://www.tqip.com/)进行专利授权等付费操作:https://www.patentguru.com/cn/search?q=一种自定义提取流程的服务封装系统
|
||||
本软件仅供学习交流使用,严禁使用软件进行任何违法违规的操作,如爬取不允许爬取的政府/军事机关网站等。使用本软件所造成的一切后果由使用者自负,与作者本人无关,作者不会承担任何责任。同时,软件受到专利权保护,如要用于商业用途,如使用软件进行盈利接单,用于公司业务,或出售采集到的数据等,请邮件联系作者:naibowang@foxmail.com进行专利授权等付费操作:https://www.patentguru.com/cn/search?q=一种自定义提取流程的服务封装系统
|
||||
|
||||
对于个人使用者来说,易采集EasySpider是一款完全免费无广告的开源软件,软件开发和维护全靠作者用爱发电,因此您可以选择支持作者让作者有更多的热情和精力维护此软件,或者您使用了此软件进行了盈利,欢迎您通过下面的方式支持作者:
|
||||
|
||||
|
@ -167,6 +167,12 @@
|
||||
<input onkeydown="inputDelete(event)" class="form-control" v-model.number="nowNode['parameters']['scrollCount']" type="number" required></input>
|
||||
<label>Wait time after scrolling (in seconds):</label>
|
||||
<input onkeydown="inputDelete(event)" class="form-control" v-model.number="nowNode['parameters']['scrollWaitTime']" type="number" required></input>
|
||||
<label>Way to handle pop-up windows after clicking:</label>
|
||||
<p><select v-model='nowNode["parameters"]["alertHandleType"]' class="form-control">
|
||||
<option :value = 0>No pop-up window</option>
|
||||
<option :value = 1>Accept pop-up window</option>
|
||||
<option :value = 2>Reject pop-up window (only for Confirm pop-up window)</option>
|
||||
</select></p>
|
||||
<p style="margin-top: 10px">
|
||||
<a class="btn btn-primary" data-toggle="collapse" href="#collapseExample" role="button" aria-expanded="false" aria-controls="collapseExample">
|
||||
Click here to expand/collapse advanced operations
|
||||
@ -324,7 +330,7 @@
|
||||
<div class="elements" v-if="nodeType==4">
|
||||
<p><input onkeydown="inputDelete(event)" type="checkbox" v-model='nowNode["parameters"]["iframe"]'></input>Element is inside iframe</p>
|
||||
<div v-if="nowNode['isInLoop']">
|
||||
|
||||
<!-- 如果在循环内且循环内是固定文本才显示此行元素 -->
|
||||
<p><input onkeydown="inputDelete(event)" type="checkbox" v-model='useLoop'></input>Use text from the loop (If unchecked, the text entered each time will be the text from the "Input Value" text box below. If checked, it will use the text set within the loop.)</p>
|
||||
<p v-if="useLoop">
|
||||
<label>Index value (0 represents using the entire current loop text. If greater than 0, it represents the text index value separated by "~" within the current loop. For example, if the current loop text value is A~B, index value 1 represents inputting A, 2 represents inputting B, and 0 represents inputting A~B)</label>
|
||||
@ -372,11 +378,14 @@
|
||||
<option value = 5>Run Python code on current environment (the "exec" operation)</option>
|
||||
<option value = 6>Get value of a Python expression (the "eval" operation)</option>
|
||||
<option value = 7>Pause program execution (such as when the captcha box appears)</option>
|
||||
<option value = 8>Refresh page</option>
|
||||
</select>
|
||||
<div v-if='nowNode["parameters"]["codeMode"] == 7'>
|
||||
<label>This operation can pause program execution, such as when a captcha box appears, and it will not continue until you manually press and hold the p key.</label>
|
||||
<label>This operation can pause program execution, such as when a captcha box appears, and it will not continue until you manually press and hold the pause/continue shortcut key (default: key p).</label>
|
||||
</div>
|
||||
<div v-if='nowNode["parameters"]["codeMode"] == 8'>
|
||||
<label>This operation can refresh the current page.</label>
|
||||
</div>
|
||||
|
||||
<div v-if='nowNode["parameters"]["codeMode"] < 3 || nowNode["parameters"]["codeMode"] >= 5 && nowNode["parameters"]["codeMode"] <=6'>
|
||||
<label>Code (Use Field["FieldName"] to input the lastest value of a field): </label>
|
||||
<textarea onkeydown="inputDelete(event)" class="form-control" rows="2" v-model='nowNode["parameters"]["code"]' placeholder="Please input a JavaScript command or a system command. For example, document.body.innerText = '1' is an example of a JavaScript command, and python D:/test.py is an example of a system command. If you choose to execute a JavaScript script for the current iteration, you can represent the element of the current iteration using arguments[0]. For instance, arguments[0].style.color = 'blue' sets the color of the element in the current iteration to blue."></textarea>
|
||||
@ -404,6 +413,7 @@ This option is an advanced feature that allows directly returning the expression
|
||||
2. Return the value of a custom global variable: `self.myVar`
|
||||
3. Return the result of a conditional statement: `self.myVar == 1`
|
||||
4. Determining whether the value extracted from a certain field is equal to the value of a certain variable: self.outputParameters["field name"] == self.myVar
|
||||
|
||||
Please note that this feature does not support assigning values to variables. In other words, you cannot write something like `self.myVar = 1`. If you want to perform assignment operations, please select the previous option, "Run Python code on current environment (the "exec" operation)"</pre>
|
||||
<p style="margin-top: 15px">Whether to record the output/return value of the execution as a field: </p>
|
||||
<p><select v-model='nowNode["parameters"]["recordASField"]' class="form-control">
|
||||
@ -660,11 +670,11 @@ If the expression returns a value greater than 0 or evaluates to True, the opera
|
||||
</select>
|
||||
<label>Export File Name/Database Table Name (Can use ../ to represent relative path to change the file save location,the keyword "current_time" will be replaced with the timestamp when the task is executed):</label>
|
||||
<input onkeydown="inputDelete(event)" value="current_time" id="saveName" class="form-control"></input>
|
||||
<label>Is it an extreme anti-scraping website like Cloudflare (<a href="https://www.bilibili.com/video/BV1Ph4y1E7R9/" target="_blank">Watch Tutorial</a>)?</label>
|
||||
<select id="cloudflare" name="cloudflare" class="form-control">
|
||||
<option value=0>No</option>
|
||||
<option value=1>Yes (Only support on Windows x64 platform)</option>
|
||||
</select>
|
||||
<!-- <label>Is it an extreme anti-scraping website like Cloudflare (<a href="https://www.bilibili.com/video/BV1Ph4y1E7R9/" target="_blank">Watch Tutorial</a>)?</label>-->
|
||||
<!-- <select id="cloudflare" name="cloudflare" class="form-control">-->
|
||||
<!-- <option value=0>No</option>-->
|
||||
<!-- <option value=1>Yes (Only support on Windows x64 platform)</option>-->
|
||||
<!-- </select>-->
|
||||
<label>To modify the input parameters of each operation during execution, read the following Excel (.xlsx) file. Please click the "Read Input Parameters from Excel File" button when calling the task to view the file format:</label>
|
||||
<input onkeydown="inputDelete(event)" id="inputExcel" name="inputExcel" class="form-control" placeholder="If left empty, input parameters will not be read from Excel. The file path is relative to the EasySpider folder, e.g., inputs/task1.xlsx"></input>
|
||||
<label>Browser Emulation Type:</label>
|
||||
@ -672,6 +682,11 @@ If the expression returns a value greater than 0 or evaluates to True, the opera
|
||||
<option value=0>Desktop</option>
|
||||
<option value=1>Mobile (Not supported under Cloudflare mode)</option>
|
||||
</select>
|
||||
<label>Whether to maximize the browser window:</label>
|
||||
<select id="maximizeWindow" name="maximizeWindow" class="form-control">
|
||||
<option value = 0>No</option>
|
||||
<option value = 1>Yes</option>
|
||||
</select>
|
||||
<label>Save Data Every N Rows (Specify N below, the larger the value, the faster the scraping speed, but there is a risk of data loss if unexpectedly exited):</label>
|
||||
<input onkeydown="inputDelete(event)" type="number" value="10" id="saveThreshold" name="saveThreshold" class="form-control"></input>
|
||||
<label>Do you want to resume execution from the last saved position when unexpectedly exiting and restarting the task (The record interval of the number of collected items is the value set above)?</label>
|
||||
@ -686,6 +701,8 @@ If the expression returns a value greater than 0 or evaluates to True, the opera
|
||||
<option value = 1>Yes</option>
|
||||
<option value = 0>No</option>
|
||||
</select>
|
||||
<label>Pause/Continue Task Shortcut Key:</label>
|
||||
<input onkeydown="inputDelete(event)" type="text" value="p" id="pauseKey" class="form-control"></input>
|
||||
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
|
@ -30,7 +30,7 @@ nodeList.push(root);
|
||||
let queue = new Array();
|
||||
let actionSequence = new Array(); //存储图结构,每个元素为在nodelist里面的索引值,下面的id和pid根据此数组进行索引,然后再在nodelist里找
|
||||
let nowNode = null; //存储现在所在的节点
|
||||
let vueData = { nowNodeIndex: 0 }; //存储目前所在节点的索引号,不能直接使用变量而需要用对象包起来
|
||||
let vueData = {nowNodeIndex: 0}; //存储目前所在节点的索引号,不能直接使用变量而需要用对象包起来
|
||||
let option = 0; //工具箱选项
|
||||
let title = "";
|
||||
let parameterNum = 1; //记录目前的参数个数
|
||||
@ -41,20 +41,20 @@ let parameterNum = 1; //记录目前的参数个数
|
||||
let app = new Vue({
|
||||
el: '#app',
|
||||
data: {
|
||||
list: { nl: nodeList },
|
||||
list: {nl: nodeList},
|
||||
index: vueData,
|
||||
nodeType: 0, // 当前元素的类型
|
||||
nowNode: null, // 用来临时存储元素的节点
|
||||
codeMode: 0, //代码模式
|
||||
loopType: -1, //点击循环时候用来循环选项
|
||||
useLoop: false, //记录是否使用循环内元素
|
||||
nowArrow: { "position": -1, "pId": 0, "num": 0 },
|
||||
paras: { "parameters": [] }, //提取数据的参数列表
|
||||
nowArrow: {"position": -1, "pId": 0, "num": 0},
|
||||
paras: {"parameters": []}, //提取数据的参数列表
|
||||
TClass: -1, //条件分支的条件类别
|
||||
paraIndex: 0, //当前参数的index
|
||||
XPaths: "", //xpath列表
|
||||
},
|
||||
mounted: function() {
|
||||
mounted: function () {
|
||||
// setTimeout(function () {
|
||||
// $("#flowchart_graph")[0].scrollTo(0, 10000);
|
||||
// window.scrollTo(0, 10000);
|
||||
@ -64,7 +64,7 @@ let app = new Vue({
|
||||
watch: {
|
||||
nowArrow: { //变量发生变化的时候进行一些操作
|
||||
deep: true,
|
||||
handler: function(newVal, oldVal) {
|
||||
handler: function (newVal, oldVal) {
|
||||
let arrlist = document.getElementsByClassName("arrow");
|
||||
if (oldVal != null) {
|
||||
for (let i = 0; i < arrlist.length; i++) {
|
||||
@ -84,34 +84,34 @@ let app = new Vue({
|
||||
}
|
||||
}
|
||||
},
|
||||
nowNode:{
|
||||
deep:true,
|
||||
handler: function(newVal, oldVal) {
|
||||
nowNode: {
|
||||
deep: true,
|
||||
handler: function (newVal, oldVal) {
|
||||
updateUI();
|
||||
}
|
||||
},
|
||||
loopType: { //循环类型发生变化的时候更新参数值
|
||||
handler: function(newVal, oldVal) {
|
||||
handler: function (newVal, oldVal) {
|
||||
this.nowNode["parameters"]["loopType"] = newVal;
|
||||
}
|
||||
},
|
||||
TClass: {
|
||||
handler: function(newVal, oldVal) {
|
||||
handler: function (newVal, oldVal) {
|
||||
this.nowNode["parameters"]["class"] = newVal;
|
||||
}
|
||||
},
|
||||
useLoop: {
|
||||
handler: function(newVal, oldVal) {
|
||||
handler: function (newVal, oldVal) {
|
||||
this.nowNode["parameters"]["useLoop"] = newVal;
|
||||
}
|
||||
},
|
||||
paras: {
|
||||
handler: function(newVal, oldVal) {
|
||||
handler: function (newVal, oldVal) {
|
||||
this.nowNode["parameters"]["paras"] = newVal["parameters"];
|
||||
}
|
||||
},
|
||||
codeMode: {
|
||||
handler: function(newVal, oldVal) {
|
||||
handler: function (newVal, oldVal) {
|
||||
this.nowNode["parameters"]["codeMode"] = newVal;
|
||||
// if(newVal == 3){
|
||||
// this.nowNode["title"] = LANG("退出循环", "Exit Loop");
|
||||
@ -124,28 +124,28 @@ let app = new Vue({
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getCookies: function() { //获取cookies
|
||||
let command = new WebSocket("ws://localhost:"+getUrlParam("wsport"))
|
||||
command.onopen = function() {
|
||||
getCookies: function () { //获取cookies
|
||||
let command = new WebSocket("ws://localhost:" + getUrlParam("wsport"))
|
||||
command.onopen = function () {
|
||||
let message = {
|
||||
type: 7, //消息类型,0代表连接操作
|
||||
};
|
||||
this.send(JSON.stringify(message));
|
||||
};
|
||||
},
|
||||
changeXPaths: function (XPaths){
|
||||
changeXPaths: function (XPaths) {
|
||||
let result = "";
|
||||
for (let i = 0; i < XPaths.length; i++) {
|
||||
result += XPaths[i] + "\n";
|
||||
}
|
||||
this.XPaths = result;
|
||||
},
|
||||
addPara: function() { //添加参数
|
||||
addPara: function () { //添加参数
|
||||
this.nowNode["parameters"]["paras"].push({
|
||||
"nodeType": 0,
|
||||
"contentType": 0,
|
||||
"relative": false,
|
||||
"name": LANG("自定义参数_" + parameterNum.toString(),"Custom_Field_" + parameterNum.toString()),
|
||||
"name": LANG("自定义参数_" + parameterNum.toString(), "Custom_Field_" + parameterNum.toString()),
|
||||
"desc": "",
|
||||
"extractType": 0,
|
||||
"relativeXPath": "//body",
|
||||
@ -169,34 +169,35 @@ let app = new Vue({
|
||||
});
|
||||
notifyParameterNum(1);
|
||||
this.paraIndex = this.nowNode["parameters"]["paras"].length - 1;
|
||||
setTimeout(function(){$("#app > div.elements > div.toolkitcontain > table.toolkittb4 > tbody > tr:last-child")[0].scrollIntoView(false); //滚动到底部
|
||||
setTimeout(function () {
|
||||
$("#app > div.elements > div.toolkitcontain > table.toolkittb4 > tbody > tr:last-child")[0].scrollIntoView(false); //滚动到底部
|
||||
}, 200);
|
||||
},
|
||||
modifyParas: function(i) { //修改第i个参数
|
||||
modifyParas: function (i) { //修改第i个参数
|
||||
this.paraIndex = i;
|
||||
console.log(this.paras);
|
||||
},
|
||||
deleteParas: function(i) { //删除第i个参数
|
||||
deleteParas: function (i) { //删除第i个参数
|
||||
this.nowNode["parameters"]["paras"].splice(i, 1);
|
||||
//如果参数删除完了,就把提取数据也删掉
|
||||
if (this.nowNode["parameters"]["paras"].length == 0) {
|
||||
deleteElement();
|
||||
}
|
||||
},
|
||||
upParas: function(i) { //上移第i个参数
|
||||
upParas: function (i) { //上移第i个参数
|
||||
if (i != 0) {
|
||||
let t = this.nowNode["parameters"]["paras"].splice(i, 1)[0];
|
||||
this.nowNode["parameters"]["paras"].splice(i - 1, 0, t);
|
||||
}
|
||||
},
|
||||
downParas: function(i) { //下移第i个参数
|
||||
downParas: function (i) { //下移第i个参数
|
||||
if (i != this.nowNode["parameters"]["paras"].length - 1) {
|
||||
let t = this.nowNode["parameters"]["paras"].splice(i, 1)[0];
|
||||
this.nowNode["parameters"]["paras"].splice(i + 1, 0, t);
|
||||
}
|
||||
},
|
||||
|
||||
getType: function(nodeType, contentType) { //根据类型得到字段名称
|
||||
getType: function (nodeType, contentType) { //根据类型得到字段名称
|
||||
if (contentType == 2) {
|
||||
return "InnerHTML";
|
||||
} else if (contentType == 3) {
|
||||
@ -205,11 +206,11 @@ let app = new Vue({
|
||||
if (nodeType == 2) {
|
||||
return LANG("链接地址", "Link Address");
|
||||
} else if (nodeType == 1) {
|
||||
return LANG("链接文本","Link Text");
|
||||
return LANG("链接文本", "Link Text");
|
||||
} else if (nodeType == 4) {
|
||||
return LANG("图片地址","Image Address");
|
||||
return LANG("图片地址", "Image Address");
|
||||
} else {
|
||||
return LANG("文本","Text");
|
||||
return LANG("文本", "Text");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -268,16 +269,16 @@ function newNode(node) {
|
||||
}
|
||||
|
||||
|
||||
|
||||
function arrowMouseDown(e) {
|
||||
if (e.button == 2) //右键点击
|
||||
{
|
||||
if (option != 0) {
|
||||
app._data.nowArrow = { "position": this.getAttribute('position'), "pId": this.getAttribute('pId'), "num": 0 };
|
||||
app._data.nowArrow = {"position": this.getAttribute('position'), "pId": this.getAttribute('pId'), "num": 0};
|
||||
}
|
||||
toolBoxKernel(e);
|
||||
}
|
||||
}
|
||||
|
||||
//增加分支点击事件
|
||||
function branchClick(e) {
|
||||
let judgeId = this.getAttribute('data');
|
||||
@ -288,7 +289,7 @@ function branchClick(e) {
|
||||
parentId: 0,
|
||||
type: 3,
|
||||
option: 10,
|
||||
title: LANG("条件分支"+(nodeList[actionSequence[judgeId]]["sequence"].length+1).toString(), "Condition "+(l+1).toString()),
|
||||
title: LANG("条件分支" + (nodeList[actionSequence[judgeId]]["sequence"].length + 1).toString(), "Condition " + (l + 1).toString()),
|
||||
sequence: [],
|
||||
isInLoop: false,
|
||||
};
|
||||
@ -297,7 +298,7 @@ function branchClick(e) {
|
||||
// nodeList[actionSequence[judgeId]]["sequence"].splice(0, 0, t.index); //开头插入
|
||||
nodeList[actionSequence[judgeId]]["sequence"].push(t.index); //结尾插入
|
||||
refresh();
|
||||
app._data.nowArrow = { "position": -1, "pId": t["id"], "num": 0 };
|
||||
app._data.nowArrow = {"position": -1, "pId": t["id"], "num": 0};
|
||||
$("#" + t["id"]).click();
|
||||
e.stopPropagation(); //防止冒泡
|
||||
}
|
||||
@ -305,7 +306,7 @@ function branchClick(e) {
|
||||
function elementMousedown(e) {
|
||||
if (e.button == 2) //右键点击
|
||||
{
|
||||
try{
|
||||
try {
|
||||
document.getElementById("contextMenu").remove();
|
||||
} catch {
|
||||
|
||||
@ -323,7 +324,7 @@ function elementMousedown(e) {
|
||||
|
||||
//元素点击事件
|
||||
function elementClick(e) {
|
||||
try{
|
||||
try {
|
||||
document.getElementById("contextMenu").remove();
|
||||
} catch (e) {
|
||||
|
||||
@ -341,7 +342,7 @@ function elementClick(e) {
|
||||
//箭头点击事件
|
||||
function arrowClick(e) {
|
||||
if (option != 0) {
|
||||
app._data.nowArrow = { "position": this.getAttribute('position'), "pId": this.getAttribute('pId'), "num": 0 };
|
||||
app._data.nowArrow = {"position": this.getAttribute('position'), "pId": this.getAttribute('pId'), "num": 0};
|
||||
}
|
||||
toolBoxKernel(e);
|
||||
}
|
||||
@ -382,7 +383,7 @@ function toolBoxKernel(e, para = null) {
|
||||
let pId2 = app._data.nowArrow['pId'];
|
||||
nodeList[actionSequence[pId2]]["sequence"].splice(position2 + 1, 0, t.index); //在相应位置添加新元素
|
||||
refresh(); //重新渲染页面
|
||||
app._data.nowArrow = { "position": t["position"], "pId": t["parentId"], "num": 0 };
|
||||
app._data.nowArrow = {"position": t["position"], "pId": t["parentId"], "num": 0};
|
||||
$("#" + t["id"]).click(); //复制后点击复制后的元素
|
||||
e.stopPropagation(); //防止冒泡
|
||||
}
|
||||
@ -419,7 +420,11 @@ function toolBoxKernel(e, para = null) {
|
||||
nodeList[actionSequence[pId2]]["sequence"].splice(position2 + 1, 0, element[0]); //在相应位置添加新元素
|
||||
refresh(); //重新渲染页面
|
||||
// console.log(nodeList[element[0]]);
|
||||
app._data.nowArrow = { "position": nodeList[element[0]]["position"], "pId": nodeList[element[0]]["parentId"], "num": 0 };
|
||||
app._data.nowArrow = {
|
||||
"position": nodeList[element[0]]["position"],
|
||||
"pId": nodeList[element[0]]["parentId"],
|
||||
"num": 0
|
||||
};
|
||||
$("#" + nodeList[element[0]]["id"]).click();
|
||||
} else {
|
||||
showError(LANG("自己不能移动到自己的节点里!", "Cannot move inside self!"));
|
||||
@ -432,7 +437,7 @@ function toolBoxKernel(e, para = null) {
|
||||
let nt2 = null;
|
||||
if (option == 2 || option == 7) { //点击元素或移动到元素操作的名称更改
|
||||
let l = 6;
|
||||
if(option == 2){
|
||||
if (option == 2) {
|
||||
title = LANG("点击", "Click ");
|
||||
} else {
|
||||
title = LANG("移动到", "Move to ");
|
||||
@ -440,7 +445,7 @@ function toolBoxKernel(e, para = null) {
|
||||
}
|
||||
try {
|
||||
content = para["content"];
|
||||
} catch{
|
||||
} catch {
|
||||
content = LANG("元素", " Element");
|
||||
}
|
||||
|
||||
@ -449,7 +454,7 @@ function toolBoxKernel(e, para = null) {
|
||||
title += LANG("元素", "Element");
|
||||
} else {
|
||||
let lang = detectLang(str);
|
||||
if(lang == 1){ //中文
|
||||
if (lang == 1) { //中文
|
||||
if (str.length > l) {
|
||||
str = str.substring(0, l) + "...";
|
||||
}
|
||||
@ -511,18 +516,18 @@ function toolBoxKernel(e, para = null) {
|
||||
nodeList[actionSequence[pId]]["sequence"].splice(position + 1, 0, t.index); //在相应位置添加新元素
|
||||
refresh(); //重新渲染页面
|
||||
//下面是确定添加元素之后下一个要插入的节点的位置
|
||||
app._data.nowArrow = { "position": t["position"], "pId": t["parentId"], "num": 0 };
|
||||
app._data.nowArrow = {"position": t["position"], "pId": t["parentId"], "num": 0};
|
||||
addParameters(t); //增加选项的默认参数
|
||||
if (para != null) {
|
||||
modifyParameters(t, para);
|
||||
}
|
||||
if (option == 8) //循环情况下应插入在循环里面
|
||||
{
|
||||
app._data.nowArrow = { "position": -1, "pId": t["id"], "num": 0 };
|
||||
app._data.nowArrow = {"position": -1, "pId": t["id"], "num": 0};
|
||||
$("#" + t["id"]).click();
|
||||
} else if (option == 9) //判断插入到第一个判断条件中
|
||||
{
|
||||
app._data.nowArrow = { "position": -1, "pId": nt["id"], "num": 0 };
|
||||
app._data.nowArrow = {"position": -1, "pId": nt["id"], "num": 0};
|
||||
$("#" + nt["id"]).click();
|
||||
} else {
|
||||
$("#" + t["id"]).click();
|
||||
@ -537,7 +542,7 @@ function toolBoxKernel(e, para = null) {
|
||||
|
||||
}
|
||||
|
||||
$(".options").mousedown(function() {
|
||||
$(".options").mousedown(function () {
|
||||
option = parseInt(this.getAttribute("data"));
|
||||
title = this.innerHTML;
|
||||
if (option >= 10 && option <= 12 && (nowNode == null || nowNode.getAttribute("id") == 0)) {
|
||||
@ -550,7 +555,7 @@ $(".options").mousedown(function() {
|
||||
|
||||
function arrowDragOver(e) {
|
||||
e.preventDefault();
|
||||
app._data.nowArrow = { "position": this.getAttribute('position'), "pId": this.getAttribute('pId'), "num": 0 };
|
||||
app._data.nowArrow = {"position": this.getAttribute('position'), "pId": this.getAttribute('pId'), "num": 0};
|
||||
// console.log("dragover", app._data.nowArrow, nowNode);
|
||||
}
|
||||
|
||||
@ -571,7 +576,7 @@ function elementDragEnd(e) {
|
||||
|
||||
function arrowDblClick(e) {
|
||||
option = 13; //调整锚点操作
|
||||
app._data.nowArrow = { "position": this.getAttribute('position'), "pId": this.getAttribute('pId'), "num": 0 };
|
||||
app._data.nowArrow = {"position": this.getAttribute('position'), "pId": this.getAttribute('pId'), "num": 0};
|
||||
toolBoxKernel.call(this, e);
|
||||
e.stopPropagation();
|
||||
}
|
||||
@ -655,7 +660,7 @@ function refresh(nowArrowReset = true) {
|
||||
}
|
||||
if (nowArrowReset) //如果要重置锚点位置
|
||||
{
|
||||
app._data.nowArrow = { "position": nodeList[0].sequence.length - 1, "pId": 0, "num": 0 }; //设置默认要添加的位置是元素流程最开头处
|
||||
app._data.nowArrow = {"position": nodeList[0].sequence.length - 1, "pId": 0, "num": 0}; //设置默认要添加的位置是元素流程最开头处
|
||||
}
|
||||
//第一个元素不渲染
|
||||
for (let i = 1; i < actionSequence.length; i++) {
|
||||
@ -694,32 +699,32 @@ function deleteElement() {
|
||||
pId = nowNode.parentNode.parentNode.getAttribute('pId');
|
||||
position = nowNode.parentNode.parentNode.getAttribute('position');
|
||||
}
|
||||
app.$data.nowArrow = { position: position - 1, "pId": pId, "num": 0 }; //删除元素后锚点跳转到当前元素的上一个节点
|
||||
app.$data.nowArrow = {position: position - 1, "pId": pId, "num": 0}; //删除元素后锚点跳转到当前元素的上一个节点
|
||||
refresh(false); //重新渲染页面
|
||||
nowNode = null; //取消选择
|
||||
}
|
||||
|
||||
document.getElementById("flowchart_graph").oncontextmenu = function(e) {
|
||||
document.getElementById("flowchart_graph").oncontextmenu = function (e) {
|
||||
// 创建一个包含删除选项的右键菜单
|
||||
let contextMenu = document.createElement("div");
|
||||
contextMenu.id = "contextMenu";
|
||||
contextMenu.innerHTML = `<div>${LANG("试运行","Test Run")}</div>
|
||||
<div>${LANG("复制元素","Copy Element")}</div>
|
||||
<div>${LANG("剪切元素","Move Element")}</div>
|
||||
contextMenu.innerHTML = `<div>${LANG("试运行", "Test Run")}</div>
|
||||
<div>${LANG("复制元素", "Copy Element")}</div>
|
||||
<div>${LANG("剪切元素", "Move Element")}</div>
|
||||
<div>${LANG("删除元素(双击)", "Delete Element (Double Click)")}</div>`;
|
||||
|
||||
if (nowNode.getAttribute("datatype") == 3) { //如果删掉的是条件分支的话
|
||||
contextMenu.innerHTML += `<div>${LANG("前移","Move Up")}</div>
|
||||
<div>${LANG("后移","Move Down")}</div>`;
|
||||
contextMenu.innerHTML += `<div>${LANG("前移", "Move Up")}</div>
|
||||
<div>${LANG("后移", "Move Down")}</div>`;
|
||||
// Add 前移 functionality
|
||||
contextMenu.children[4].addEventListener('click', function () {
|
||||
let conditionId = parseInt(nowNode.getAttribute('pid'));
|
||||
let position = parseInt(nowNode.getAttribute('position'));
|
||||
if(position > 0){
|
||||
if (position > 0) {
|
||||
nodeList[actionSequence[conditionId]]["sequence"][position] = nodeList[actionSequence[conditionId]]["sequence"][position - 1];
|
||||
nodeList[actionSequence[conditionId]]["sequence"][position - 1] = actionSequence[parseInt(nowNode.getAttribute('data'))];
|
||||
refresh();
|
||||
app._data.nowArrow = { "position": -1, "pId": 0, "num": 0 };
|
||||
app._data.nowArrow = {"position": -1, "pId": 0, "num": 0};
|
||||
$("#0").click();
|
||||
e.stopPropagation(); //防止冒泡
|
||||
}
|
||||
@ -729,11 +734,11 @@ document.getElementById("flowchart_graph").oncontextmenu = function(e) {
|
||||
contextMenu.children[5].addEventListener('click', function () {
|
||||
let conditionId = parseInt(nowNode.getAttribute('pid'));
|
||||
let position = parseInt(nowNode.getAttribute('position'));
|
||||
if(position < nodeList[actionSequence[conditionId]]["sequence"].length - 1){
|
||||
if (position < nodeList[actionSequence[conditionId]]["sequence"].length - 1) {
|
||||
nodeList[actionSequence[conditionId]]["sequence"][position] = nodeList[actionSequence[conditionId]]["sequence"][position + 1];
|
||||
nodeList[actionSequence[conditionId]]["sequence"][position + 1] = actionSequence[parseInt(nowNode.getAttribute('data'))];
|
||||
refresh();
|
||||
app._data.nowArrow = { "position": -1, "pId": 0, "num": 0 };
|
||||
app._data.nowArrow = {"position": -1, "pId": 0, "num": 0};
|
||||
$("#0").click();
|
||||
e.stopPropagation(); //防止冒泡
|
||||
}
|
||||
@ -748,7 +753,7 @@ document.getElementById("flowchart_graph").oncontextmenu = function(e) {
|
||||
contextMenu.style.width = LANG("180px", "250px");
|
||||
|
||||
// 添加删除元素的功能
|
||||
contextMenu.children[3].addEventListener("dblclick", function() {
|
||||
contextMenu.children[3].addEventListener("dblclick", function () {
|
||||
// myElement.remove(); // 删除元素
|
||||
deleteElement();
|
||||
contextMenu.remove(); // 删除右键菜单
|
||||
@ -775,8 +780,8 @@ document.getElementById("flowchart_graph").oncontextmenu = function(e) {
|
||||
|
||||
// 将右键菜单添加到文档中
|
||||
document.body.appendChild(contextMenu);
|
||||
} //屏蔽右键菜单
|
||||
//删除元素
|
||||
} //屏蔽右键菜单
|
||||
//删除元素
|
||||
|
||||
|
||||
function inputDelete(e) {
|
||||
|
@ -123,7 +123,7 @@
|
||||
<input onkeydown="inputDelete(event)" class="form-control" v-model.number="nowNode['parameters']['scrollWaitTime']" type="number" required></input>
|
||||
<p style="margin-top: 10px">
|
||||
<a class="btn btn-primary" data-toggle="collapse" href="#collapseOpenPage" role="button" aria-expanded="false" aria-controls="collapseExample">
|
||||
点此展开/折叠高级操作
|
||||
点此展开/折叠自定义操作
|
||||
</a>
|
||||
</p>
|
||||
<div :class="{collapse: true, 'show': nowNode['parameters']['cookies'].length!=0}" id="collapseOpenPage">
|
||||
@ -167,9 +167,15 @@
|
||||
<input onkeydown="inputDelete(event)" class="form-control" v-model.number="nowNode['parameters']['scrollCount']" type="number" required></input>
|
||||
<label>滚动后等待时间(秒):</label>
|
||||
<input onkeydown="inputDelete(event)" class="form-control" v-model.number="nowNode['parameters']['scrollWaitTime']" type="number" required></input>
|
||||
<label>点击元素后如有弹窗出现,弹窗处理方式:</label>
|
||||
<p><select v-model='nowNode["parameters"]["alertHandleType"]' class="form-control">
|
||||
<option :value = 0>无弹窗</option>
|
||||
<option :value = 1>接受弹窗(点击弹窗确定按钮)</option>
|
||||
<option :value = 2>拒绝弹窗(点击弹窗取消按钮,仅限Confirm弹框)</option>
|
||||
</select></p>
|
||||
<p style="margin-top: 10px">
|
||||
<a class="btn btn-primary" data-toggle="collapse" href="#collapseExample" role="button" aria-expanded="false" aria-controls="collapseExample">
|
||||
点此展开/折叠高级操作
|
||||
点此展开/折叠自定义操作
|
||||
</a>
|
||||
</p>
|
||||
<div :class="{collapse: true, 'show': nowNode['parameters']['beforeJS'].length!=0 || nowNode['parameters']['afterJS'].length!=0}" id="collapseExample">
|
||||
@ -229,7 +235,7 @@
|
||||
<p><button type="button" data-toggle="modal" data-target="#myModal_XPath" @click="changeXPaths(paras.parameters[paraIndex]['allXPaths'])" class="btn btn-primary" style="margin-top: 10px">点此查看其他等价的XPath</button></p>
|
||||
<p style="margin-top: 10px">
|
||||
<a class="btn btn-primary" data-toggle="collapse" href="#elementAdvanced" role="button" aria-expanded="false" aria-controls="collapseExample">
|
||||
点此展开/折叠高级操作
|
||||
点此展开/折叠自定义操作
|
||||
</a>
|
||||
</p>
|
||||
<div :class="{collapse: true, 'show': paras.parameters[paraIndex]['beforeJS'].length!=0 || paras.parameters[paraIndex]['afterJS'].length!=0}" id="elementAdvanced">
|
||||
@ -289,7 +295,6 @@
|
||||
<label>最长等待脚本执行时间(0代表无限等待): </label>
|
||||
<input onkeydown="inputDelete(event)" required class="form-control" type="number" v-model.number='paras.parameters[paraIndex]["JSWaitTime"]'></input>
|
||||
</div>
|
||||
|
||||
<label>节点类型</label>
|
||||
<select v-model='paras.parameters[paraIndex]["nodeType"]' class="form-control">
|
||||
<option :value = 0>普通节点</option>
|
||||
@ -343,7 +348,7 @@
|
||||
<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 style="margin-top: 10px">
|
||||
<a class="btn btn-primary" data-toggle="collapse" href="#inputAdvanced" role="button" aria-expanded="false" aria-controls="collapseExample">
|
||||
点此展开/折叠高级操作
|
||||
点此展开/折叠自定义操作
|
||||
</a>
|
||||
</p>
|
||||
<div :class="{collapse: true, 'show': nowNode['parameters']['beforeJS'].length!=0 || nowNode['parameters']['afterJS'].length!=0}" id="inputAdvanced">
|
||||
@ -373,9 +378,13 @@
|
||||
<option value = 5>在执行环境下运行Python代码(exec操作)</option>
|
||||
<option value = 6>在执行环境下获得Python表达式值(eval操作)</option>
|
||||
<option value = 7>暂停程序执行(如检测到验证码框出现时暂停执行)</option>
|
||||
<option value = 8>刷新页面</option>
|
||||
</select>
|
||||
<div v-if='nowNode["parameters"]["codeMode"] == 7'>
|
||||
<label>此操作可以暂停程序执行,如检测到验证码框出现时暂停执行,直到手动长按键盘p键后才继续执行。</label>
|
||||
<label>此操作可以暂停程序执行,如检测到验证码框出现时暂停执行,直到手动长按键盘自定义暂停/继续快捷键(默认:p键)后才继续执行。</label>
|
||||
</div>
|
||||
<div v-if='nowNode["parameters"]["codeMode"] == 8'>
|
||||
<label>此操作可以刷新当前页面。</label>
|
||||
</div>
|
||||
<div v-if='nowNode["parameters"]["codeMode"] < 3 || nowNode["parameters"]["codeMode"] >= 5 && nowNode["parameters"]["codeMode"] <=6'>
|
||||
<label>代码/脚本内容(用Field["字段名"]来输入某字段/自定义操作的最新提取/返回值): </label>
|
||||
@ -537,7 +546,7 @@ print(emotlib.emoji()) # 使用其中的函数。
|
||||
|
||||
<div id="breakAdvanced" v-if='nowNode["parameters"]["loopType"] < 5'>
|
||||
<div>
|
||||
<p><label>(高级操作)使用代码/脚本定义循环退出条件(也可以在流程中添加<b>自定义操作</b>,然后选择<b>退出循环</b>选项): </label></p>
|
||||
<p><label>(自定义操作)使用代码/脚本定义循环退出条件(也可以在流程中添加<b>自定义操作</b>,然后选择<b>退出循环</b>选项): </label></p>
|
||||
<select v-model='nowNode["parameters"]["breakMode"]' class="form-control" style="font-weight: bold">
|
||||
<option value = 0>不设置脚本(选择这个下面写了脚本也不会执行)</option>
|
||||
<option value = 1>JavaScript脚本返回值(需以return 开头)</option>
|
||||
@ -661,11 +670,11 @@ print(emotlib.emoji()) # 使用其中的函数。
|
||||
</select>
|
||||
<label>导出文件名/数据库表格名称(可使用../表示相对路径以改变文件保存位置,名称中的“current_time”会被替换为执行任务时的时间戳):</label>
|
||||
<input onkeydown="inputDelete(event)" value="current_time" id="saveName" class="form-control"></input>
|
||||
<label>是否为Cloudflare等极端反爬网站(<a href="https://www.bilibili.com/video/BV1Ph4y1E7R9/" target="_blank">查看Cloudflare设计和执行教程</a>):</label>
|
||||
<select id="cloudflare" name="cloudflare" class="form-control">
|
||||
<option value = 0>否</option>
|
||||
<option value = 1>是(只支持Windows x64系统)</option>
|
||||
</select>
|
||||
<!-- <label>是否为Cloudflare等极端反爬网站(<a href="https://www.bilibili.com/video/BV1Ph4y1E7R9/" target="_blank">查看Cloudflare设计和执行教程</a>):</label>-->
|
||||
<!-- <select id="cloudflare" name="cloudflare" class="form-control">-->
|
||||
<!-- <option value = 0>否</option>-->
|
||||
<!-- <option value = 1>是(只支持Windows x64系统)</option>-->
|
||||
<!-- </select>-->
|
||||
<label>执行时通过读取以下Excel(.xlsx)文件来修改各个操作的输入参数,文件格式请在调用任务时点击“从Excel文件读取输入参数”按钮查看:</label>
|
||||
<input onkeydown="inputDelete(event)" id="inputExcel" name="inputExcel" class="form-control" placeholder="为空则不从Excel读取输入参数,文件路径相对于EasySpider文件夹,如inputs/task1.xlsx"></input>
|
||||
<label>浏览器模拟类型:</label>
|
||||
@ -673,6 +682,11 @@ print(emotlib.emoji()) # 使用其中的函数。
|
||||
<option value = 0>电脑端</option>
|
||||
<option value = 1>手机端(Cloudflare模式下不支持)</option>
|
||||
</select>
|
||||
<label>是否最大化浏览器窗口:</label>
|
||||
<select id="maximizeWindow" name="maximizeWindow" class="form-control">
|
||||
<option value = 0>否</option>
|
||||
<option value = 1>是</option>
|
||||
</select>
|
||||
<label>每采集多少条数据保存一次(值越大采集速度越快,但如果意外退出则有数据丢失风险):</label>
|
||||
<input onkeydown="inputDelete(event)" type="number" value="10" id="saveThreshold" name="saveThreshold" class="form-control"></input>
|
||||
<label>是否要意外退出并重新执行任务时从上次保存的位置继续执行(已采集条数记录间隔为上面设置的值):</label>
|
||||
@ -687,6 +701,8 @@ print(emotlib.emoji()) # 使用其中的函数。
|
||||
<option value = 1>是</option>
|
||||
<option value = 0>否</option>
|
||||
</select>
|
||||
<label>任务暂停/继续快捷键:</label>
|
||||
<input onkeydown="inputDelete(event)" type="text" value="p" id="pauseKey" class="form-control"></input>
|
||||
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
|
@ -191,6 +191,7 @@ function addParameters(t) {
|
||||
t["parameters"]["beforeJSWaitTime"] = 0; //执行前js等待时间
|
||||
t["parameters"]["afterJS"] = ""; //执行后执行的js
|
||||
t["parameters"]["afterJSWaitTime"] = 0; //执行后js等待时间
|
||||
t["parameters"]["alertHandleType"] = 0; //弹窗处理类型,1代表确认,2代表取消
|
||||
} else if (t.option == 3) { //提取数据
|
||||
t["parameters"]["clear"] = 0; //清空其他字段数据
|
||||
t["parameters"]["newLine"] = 1; //生成新行
|
||||
@ -288,6 +289,14 @@ function modifyParameters(t, para) {
|
||||
t["parameters"]["allXPaths"] = para["allXPaths"];
|
||||
if (para["nextPage"]) { //循环点击下一页的情况下
|
||||
t["title"] = LANG("循环点击下一页", "Loop click next page");
|
||||
} else if (para["type"] == "loopClickSingle") { //循环点击单个元素
|
||||
t["title"] = LANG("循环点击单个元素", "Loop click single element");
|
||||
} else if (para["type"] == "loopClickEvery") { //循环点击每个元素
|
||||
t["title"] = LANG("循环点击每个元素", "Loop click every element");
|
||||
} else if (para["type"] == "loopMouseMove") { //循环移动到单个元素
|
||||
t["title"] = LANG("循环移动到每个元素", "Loop move to every element");
|
||||
} else if (para["type"] == "multiCollectWithPattern"){
|
||||
t["title"] = LANG("循环采集数据", "Loop collect data");
|
||||
} else {
|
||||
t["title"] = LANG("循环", "Loop");
|
||||
}
|
||||
@ -475,14 +484,16 @@ function saveService(type) {
|
||||
"update_time": new Date().toLocaleString(),
|
||||
"version": "0.6.0",
|
||||
"saveThreshold": saveThreshold,
|
||||
"cloudflare": cloudflare,
|
||||
// "cloudflare": cloudflare,
|
||||
"environment": environment,
|
||||
"maximizeWindow": parseInt($("#maximizeWindow").val()),
|
||||
"maxViewLength": parseInt($("#maxViewLength").val()),
|
||||
"recordLog": parseInt($("#recordLog").val()),
|
||||
"outputFormat": $("#outputFormat").val(),
|
||||
"saveName": $("#saveName").val(),
|
||||
"inputExcel": $("#inputExcel").val(),
|
||||
"startFromExit": parseInt($("#startFromExit").val()),
|
||||
"pauseKey": $("#pauseKey").val(),
|
||||
"containJudge": containJudge,
|
||||
"desc": serviceDescription,
|
||||
"inputParameters": inputParameters,
|
||||
|
1
ElectronJS/tasks/213.json
Normal file
1
ElectronJS/tasks/213.json
Normal file
File diff suppressed because one or more lines are too long
1
ElectronJS/tasks/214.json
Normal file
1
ElectronJS/tasks/214.json
Normal file
@ -0,0 +1 @@
|
||||
{"id":214,"name":"弹窗示例","url":"http://localhost:63342/EasySpider_portal/test_pages/alert_test.html?_ijt=bhcd4kjgmci10pi60o10up8aip&_ij_reload=RELOAD_ON_SAVE","links":"https://easyspider.cn/test_pages/alert_test.html","create_time":"12/5/2023, 1:20:36 AM","update_time":"12/5/2023, 1:48:25 AM","version":"0.6.0","saveThreshold":10,"cloudflare":0,"environment":0,"maxViewLength":15,"recordLog":1,"outputFormat":"xlsx","saveName":"current_time","inputExcel":"","startFromExit":0,"containJudge":false,"desc":"http://localhost:63342/EasySpider_portal/test_pages/alert_test.html?_ijt=bhcd4kjgmci10pi60o10up8aip&_ij_reload=RELOAD_ON_SAVE","inputParameters":[{"id":0,"name":"urlList_0","nodeId":1,"nodeName":"打开网页","value":"https://easyspider.cn/test_pages/alert_test.html","desc":"要采集的网址列表,多行以\\n分开","type":"text","exampleValue":"https://easyspider.cn/test_pages/alert_test.html"}],"outputParameters":[{"id":0,"name":"参数1_文本","desc":"","type":"text","recordASField":1,"exampleValue":"\n TEST CONTENT\n"}],"graph":[{"index":0,"id":0,"parentId":0,"type":-1,"option":0,"title":"root","sequence":[1,2,3],"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":1,"index":1,"parentId":0,"type":0,"option":1,"title":"打开网页","sequence":[],"isInLoop":false,"position":0,"parameters":{"useLoop":false,"xpath":"","wait":0,"waitType":0,"beforeJS":"","beforeJSWaitTime":0,"afterJS":"","afterJSWaitTime":0,"waitElement":"","waitElementTime":10,"waitElementIframeIndex":0,"url":"http://localhost:63342/EasySpider_portal/test_pages/alert_test.html?_ijt=bhcd4kjgmci10pi60o10up8aip&_ij_reload=RELOAD_ON_SAVE","links":"https://easyspider.cn/test_pages/alert_test.html","maxWaitTime":10,"scrollType":0,"scrollCount":1,"scrollWaitTime":1,"cookies":""}},{"id":2,"index":2,"parentId":0,"type":0,"option":2,"title":"点击点击我","sequence":[],"isInLoop":false,"position":1,"parameters":{"history":4,"tabIndex":-1,"useLoop":false,"xpath":"/html/body/button[1]","iframe":false,"wait":5,"waitType":0,"beforeJS":"","beforeJSWaitTime":0,"afterJS":"","afterJSWaitTime":0,"waitElement":"","waitElementTime":10,"waitElementIframeIndex":0,"scrollType":0,"scrollCount":1,"scrollWaitTime":1,"clickWay":0,"maxWaitTime":10,"paras":[],"allXPaths":["/html/body/button[1]","//button[contains(., '点击我')]","/html/body/button"]}},{"id":3,"index":3,"parentId":0,"type":0,"option":3,"title":"提取数据","sequence":[],"isInLoop":false,"position":2,"parameters":{"history":4,"tabIndex":-1,"useLoop":false,"xpath":"","iframe":false,"wait":0,"waitType":0,"beforeJS":"","beforeJSWaitTime":0,"afterJS":"","afterJSWaitTime":0,"waitElement":"","waitElementTime":10,"waitElementIframeIndex":0,"clear":0,"newLine":1,"paras":[{"nodeType":0,"contentType":0,"relative":false,"name":"参数1_文本","desc":"","extractType":0,"relativeXPath":"/html/body/p[1]","allXPaths":["/html/body/p[1]","//p[contains(., 'TEST')]","/html/body/p"],"exampleValues":[{"num":0,"value":"\n TEST CONTENT\n"}],"unique_index":"bamas1sffoslpr6gdvm","iframe":false,"default":"","paraType":"text","recordASField":1,"beforeJS":"","beforeJSWaitTime":0,"JS":"","JSWaitTime":0,"afterJS":"","afterJSWaitTime":0,"downloadPic":0}]}},{"id":-1,"index":4,"parentId":0,"type":0,"option":5,"title":"自定义操作","sequence":[],"isInLoop":false,"position":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,"clear":0,"newLine":1,"codeMode":"8","code":"","waitTime":0,"recordASField":0,"paraType":"text","alertHandleType":1}}]}
|
1
ElectronJS/tasks/215.json
Normal file
1
ElectronJS/tasks/215.json
Normal file
@ -0,0 +1 @@
|
||||
{"id":215,"name":"弹窗示例","url":"https://easyspider.cn/test_pages/alert_test.html","links":"https://easyspider.cn/test_pages/alert_test.html","create_time":"12/5/2023, 1:53:45 AM","update_time":"12/5/2023, 2:43:40 AM","version":"0.6.0","saveThreshold":10,"cloudflare":0,"environment":0,"maxViewLength":15,"recordLog":1,"outputFormat":"xlsx","saveName":"current_time","inputExcel":"","startFromExit":0,"containJudge":false,"desc":"https://easyspider.cn/test_pages/alert_test.html","inputParameters":[{"id":0,"name":"urlList_0","nodeId":1,"nodeName":"打开网页","value":"https://easyspider.cn/test_pages/alert_test.html","desc":"要采集的网址列表,多行以\\n分开","type":"text","exampleValue":"https://easyspider.cn/test_pages/alert_test.html"}],"outputParameters":[{"id":0,"name":"参数1_文本","desc":"","type":"text","recordASField":1,"exampleValue":"\n 弹窗示例\n \n \n function showAlert() {\n alert('你已经点击了按钮!');\n }\n \n\n\n\n#wrapperToolkitIframe{\n position: absolute;\n top:0;\n}\n.toolcannotdrag{\n background-color: navy;\n width: 100%;\n text-align: center;\n font-size: 13px;\n height: 26px !important;\n padding-top: 8px !important;\n color: white;\n}\n\n\n点击我\n\n\n ✖ ✍操作台(点此拖动,左上角调整大小) \n ● 已选中1个元素,您可以:\n 确认采集 取消选择 Path: //html \n"},{"id":1,"name":"自定义操作","desc":"自定义操作返回的数据","type":"text","recordASField":0,"exampleValue":""}],"graph":[{"index":0,"id":0,"parentId":0,"type":-1,"option":0,"title":"root","sequence":[1,4,2,3],"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":1,"index":1,"parentId":0,"type":0,"option":1,"title":"打开网页","sequence":[],"isInLoop":false,"position":0,"parameters":{"useLoop":false,"xpath":"","wait":0,"waitType":0,"beforeJS":"","beforeJSWaitTime":0,"afterJS":"","afterJSWaitTime":0,"waitElement":"","waitElementTime":10,"waitElementIframeIndex":0,"url":"https://easyspider.cn/test_pages/alert_test.html","links":"https://easyspider.cn/test_pages/alert_test.html","maxWaitTime":10,"scrollType":0,"scrollCount":1,"scrollWaitTime":1,"cookies":""}},{"id":3,"index":2,"parentId":0,"type":0,"option":2,"title":"点击点击我","sequence":[],"isInLoop":false,"position":2,"parameters":{"history":4,"tabIndex":-1,"useLoop":false,"xpath":"/html/body/button[1]","iframe":false,"wait":2,"waitType":0,"beforeJS":"","beforeJSWaitTime":0,"afterJS":"","afterJSWaitTime":0,"waitElement":"","waitElementTime":10,"waitElementIframeIndex":0,"scrollType":0,"scrollCount":1,"scrollWaitTime":1,"clickWay":0,"maxWaitTime":10,"paras":[],"alertHandleType":2,"allXPaths":["/html/body/button[1]","//button[contains(., '点击我')]","/html/body/button"]}},{"id":4,"index":3,"parentId":0,"type":0,"option":3,"title":"提取数据","sequence":[],"isInLoop":false,"position":3,"parameters":{"history":4,"tabIndex":-1,"useLoop":false,"xpath":"","iframe":false,"wait":0,"waitType":0,"beforeJS":"","beforeJSWaitTime":0,"afterJS":"","afterJSWaitTime":0,"waitElement":"","waitElementTime":10,"waitElementIframeIndex":0,"clear":0,"newLine":1,"paras":[{"nodeType":0,"contentType":0,"relative":false,"name":"参数1_文本","desc":"","extractType":0,"relativeXPath":"/html/body/p[1]","allXPaths":["//html[1]","//html[contains(., '弹窗示例')]","/html"],"exampleValues":[{"num":0,"value":"\n 弹窗示例\n \n \n function showAlert() {\n alert('你已经点击了按钮!');\n }\n \n\n\n\n#wrapperToolkitIframe{\n position: absolute;\n top:0;\n}\n.toolcannotdrag{\n background-color: navy;\n width: 100%;\n text-align: center;\n font-size: 13px;\n height: 26px !important;\n padding-top: 8px !important;\n color: white;\n}\n\n\n点击我\n\n\n ✖ ✍操作台(点此拖动,左上角调整大小) \n ● 已选中1个元素,您可以:\n 确认采集 取消选择 Path: //html \n"}],"unique_index":"d8of3rzoypelpr7n0qm","iframe":false,"default":"12345","paraType":"text","recordASField":1,"beforeJS":"","beforeJSWaitTime":0,"JS":"","JSWaitTime":0,"afterJS":"alert(eval(\"self.a+2+int('1')\"))","afterJSWaitTime":0,"downloadPic":0}]}},{"id":2,"index":4,"parentId":0,"type":0,"option":5,"title":"自定义操作","sequence":[],"isInLoop":false,"position":1,"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,"clear":0,"newLine":1,"codeMode":"5","code":"self.a = 1","waitTime":0,"recordASField":0,"paraType":"text"}}]}
|
1
ElectronJS/tasks/216.json
Normal file
1
ElectronJS/tasks/216.json
Normal file
File diff suppressed because one or more lines are too long
1
ElectronJS/tasks/217.json
Normal file
1
ElectronJS/tasks/217.json
Normal file
File diff suppressed because one or more lines are too long
1
ElectronJS/tasks/218.json
Normal file
1
ElectronJS/tasks/218.json
Normal file
File diff suppressed because one or more lines are too long
1
ElectronJS/tasks/219.json
Normal file
1
ElectronJS/tasks/219.json
Normal file
@ -0,0 +1 @@
|
||||
{"id":219,"name":"Index of /files/","url":"https://www.easyspider.cn/files","links":"https://www.easyspider.cn/files","create_time":"12/5/2023, 3:49:19 AM","update_time":"12/5/2023, 5:00:23 AM","version":"0.6.0","saveThreshold":10,"environment":0,"maximizeWindow":1,"maxViewLength":15,"recordLog":1,"outputFormat":"xlsx","saveName":"current_time","inputExcel":"","startFromExit":0,"pauseKey":"t","containJudge":false,"desc":"https://www.easyspider.cn/files","inputParameters":[{"id":0,"name":"urlList_0","nodeId":1,"nodeName":"打开网页","value":"https://www.easyspider.cn/files","desc":"要采集的网址列表,多行以\\n分开","type":"text","exampleValue":"https://www.easyspider.cn/files"}],"outputParameters":[{"id":0,"name":"自定义操作","desc":"自定义操作返回的数据","type":"text","recordASField":0,"exampleValue":""}],"graph":[{"index":0,"id":0,"parentId":0,"type":-1,"option":0,"title":"root","sequence":[1,2,4,3],"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":1,"index":1,"parentId":0,"type":0,"option":1,"title":"打开网页","sequence":[],"isInLoop":false,"position":0,"parameters":{"useLoop":false,"xpath":"","wait":0,"waitType":0,"beforeJS":"","beforeJSWaitTime":0,"afterJS":"","afterJSWaitTime":0,"waitElement":"","waitElementTime":10,"waitElementIframeIndex":0,"url":"https://www.easyspider.cn/files","links":"https://www.easyspider.cn/files","maxWaitTime":10,"scrollType":0,"scrollCount":1,"scrollWaitTime":1,"cookies":""}},{"id":2,"index":2,"parentId":0,"type":0,"option":2,"title":"点击V0.5.0/","sequence":[],"isInLoop":false,"position":1,"parameters":{"history":4,"tabIndex":-1,"useLoop":false,"xpath":"/html/body/pre[1]/a[7]","iframe":false,"wait":2,"waitType":0,"beforeJS":"","beforeJSWaitTime":0,"afterJS":"","afterJSWaitTime":0,"waitElement":"","waitElementTime":10,"waitElementIframeIndex":0,"scrollType":0,"scrollCount":1,"scrollWaitTime":1,"clickWay":0,"maxWaitTime":10,"paras":[],"alertHandleType":0,"allXPaths":["/html/body/pre[1]/a[7]","//a[contains(., 'V0.5.0/')]","/html/body/pre/a"]}},{"id":4,"index":3,"parentId":0,"type":0,"option":2,"title":"点击EasySpider_0....","sequence":[],"isInLoop":false,"position":3,"parameters":{"history":5,"tabIndex":-1,"useLoop":false,"xpath":"/html/body/pre[1]/a[4]","iframe":false,"wait":2,"waitType":0,"beforeJS":"","beforeJSWaitTime":0,"afterJS":"","afterJSWaitTime":0,"waitElement":"","waitElementTime":10,"waitElementIframeIndex":0,"scrollType":0,"scrollCount":1,"scrollWaitTime":1,"clickWay":0,"maxWaitTime":10,"paras":[],"alertHandleType":0,"allXPaths":["/html/body/pre[1]/a[4]","//a[contains(., 'EasySpider')]","/html/body/pre/a[last()-1]"]}},{"id":3,"index":4,"parentId":0,"type":0,"option":5,"title":"自定义操作","sequence":[],"isInLoop":false,"position":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,"clear":0,"newLine":1,"codeMode":"7","code":"","waitTime":0,"recordASField":0,"paraType":"text"}}]}
|
1
ElectronJS/tasks/220.json
Normal file
1
ElectronJS/tasks/220.json
Normal file
File diff suppressed because one or more lines are too long
1
ElectronJS/tasks/221.json
Normal file
1
ElectronJS/tasks/221.json
Normal file
File diff suppressed because one or more lines are too long
2
ExecuteStage/.vscode/launch.json
vendored
2
ExecuteStage/.vscode/launch.json
vendored
@ -12,7 +12,7 @@
|
||||
"justMyCode": false,
|
||||
// "args": ["--id", "[7]", "--read_type", "remote", "--headless", "0"]
|
||||
// "args": ["--id", "[9]", "--read_type", "remote", "--headless", "0", "--saved_file_name", "YOUTUBE"]
|
||||
"args": ["--id", "[54]", "--headless", "0", "--user_data", "0", "--keyboard", "0"]
|
||||
"args": ["--id", "[96]", "--headless", "0", "--user_data", "0", "--keyboard", "1"]
|
||||
}
|
||||
]
|
||||
}
|
5
ExecuteStage/.vscode/settings.json
vendored
5
ExecuteStage/.vscode/settings.json
vendored
@ -1,5 +1,8 @@
|
||||
{
|
||||
"python.analysis.extraPaths": [
|
||||
"./code"
|
||||
]
|
||||
],
|
||||
"[python]": {
|
||||
"editor.defaultFormatter": null
|
||||
}
|
||||
}
|
@ -1,7 +1,8 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# import atexit
|
||||
import undetected_chromedriver as uc
|
||||
from utils import download_image, get_output_code, isnotnull, lowercase_tags_in_xpath, myMySQL, new_line, on_press_creator, on_release_creator, replace_field_values, write_to_csv, write_to_excel, write_to_json
|
||||
from utils import download_image, get_output_code, isnotnull, lowercase_tags_in_xpath, myMySQL, new_line, \
|
||||
on_press_creator, on_release_creator, replace_field_values, write_to_csv, write_to_excel, write_to_json
|
||||
from myChrome import MyChrome
|
||||
from threading import Thread, Event
|
||||
from PIL import Image
|
||||
@ -41,6 +42,7 @@ from ddddocr import DdddOcr
|
||||
from urllib.parse import urljoin
|
||||
from lxml import etree
|
||||
import onnxruntime
|
||||
|
||||
onnxruntime.set_default_logger_severity(3) # 隐藏onnxruntime的日志
|
||||
# import pandas as pd
|
||||
# import numpy
|
||||
@ -76,6 +78,12 @@ class BrowserThread(Thread):
|
||||
self.SAVED = False
|
||||
self.BREAK = False
|
||||
self.CONTINUE = False
|
||||
try:
|
||||
maximizeWindow = service["maximizeWindow"]
|
||||
except:
|
||||
maximizeWindow = 0
|
||||
if maximizeWindow == 1:
|
||||
self.browser.maximize_window()
|
||||
# 名称设定
|
||||
if saveName != "": # 命令行覆盖保存名称
|
||||
self.saveName = saveName # 保存文件的名字
|
||||
@ -94,7 +102,8 @@ class BrowserThread(Thread):
|
||||
try:
|
||||
startFromExit = service["startFromExit"] # 从上次退出的步骤开始
|
||||
if startFromExit == 1:
|
||||
with open("Data/Task_" + str(self.id) + "/" + self.saveName + '_steps.txt', 'r', encoding='utf-8-sig') as file_obj:
|
||||
with open("Data/Task_" + str(self.id) + "/" + self.saveName + '_steps.txt', 'r',
|
||||
encoding='utf-8-sig') as file_obj:
|
||||
self.startSteps = int(file_obj.read()) # 读取已执行步数
|
||||
except:
|
||||
pass
|
||||
@ -237,6 +246,10 @@ class BrowserThread(Thread):
|
||||
except:
|
||||
node["parameters"]["cookies"] = ""
|
||||
if node["option"] == 2: # 点击操作
|
||||
try:
|
||||
alertHandleType = node["parameters"]["alertHandleType"]
|
||||
except:
|
||||
node["parameters"]["alertHandleType"] = 0
|
||||
if node["parameters"]["useLoop"]:
|
||||
if self.task_version <= "0.3.5":
|
||||
# 0.3.5及以下版本的EasySpider下的循环点击不支持相对XPath
|
||||
@ -271,8 +284,10 @@ class BrowserThread(Thread):
|
||||
if para["contentType"] == 8:
|
||||
self.print_and_log(
|
||||
"默认的ddddocr识别功能如果觉得不好用,可以自行修改源码get_content函数->contentType == 8的位置换成自己想要的OCR模型然后自己编译运行;或者可以先设置采集内容类型为“元素截图”把图片保存下来,然后用自定义操作调用自己写的程序,程序的功能是读取这个最新生成的图片,然后用好用的模型,如PaddleOCR把图片识别出来,然后把返回值返回给程序作为参数输出。")
|
||||
self.print_and_log("If you think the default ddddocr function is not good enough, you can modify the source code get_content function -> contentType == 8 position to your own OCR model and then compile and run it; or you can first set the content type of the crawler to \"Element Screenshot\" to save the picture, and then call your own program with custom operations. The function of the program is to read the latest generated picture, then use a good model, such as PaddleOCR to recognize the picture, and then return the return value as a parameter output to the program.")
|
||||
if para["beforeJS"] == "" and para["afterJS"] == "" and para["contentType"] <= 1 and para["nodeType"] <= 2:
|
||||
self.print_and_log(
|
||||
"If you think the default ddddocr function is not good enough, you can modify the source code get_content function -> contentType == 8 position to your own OCR model and then compile and run it; or you can first set the content type of the crawler to \"Element Screenshot\" to save the picture, and then call your own program with custom operations. The function of the program is to read the latest generated picture, then use a good model, such as PaddleOCR to recognize the picture, and then return the return value as a parameter output to the program.")
|
||||
if para["beforeJS"] == "" and para["afterJS"] == "" and para["contentType"] <= 1 and para[
|
||||
"nodeType"] <= 2:
|
||||
para["optimizable"] = True
|
||||
else:
|
||||
para["optimizable"] = False
|
||||
@ -307,7 +322,8 @@ class BrowserThread(Thread):
|
||||
except:
|
||||
self.print_and_log("读取Excel失败,将会使用默认参数执行任务,请检查文件路径是否正确:",
|
||||
os.path.abspath(self.inputExcel))
|
||||
self.print_and_log("Failed to read Excel, will execute the task with default parameters, please check if the file path is correct: ",
|
||||
self.print_and_log(
|
||||
"Failed to read Excel, will execute the task with default parameters, please check if the file path is correct: ",
|
||||
os.path.abspath(self.inputExcel))
|
||||
time.sleep(5)
|
||||
return 0
|
||||
@ -395,11 +411,13 @@ class BrowserThread(Thread):
|
||||
# 写入日志
|
||||
# self.recordLog("持久化存储数据/Persistently store data")
|
||||
if self.log:
|
||||
with open("Data/Task_" + str(self.id) + "/" + self.saveName + '.log', 'a', encoding='utf-8-sig') as file_obj:
|
||||
with open("Data/Task_" + str(self.id) + "/" + self.saveName + '.log', 'a',
|
||||
encoding='utf-8-sig') as file_obj:
|
||||
file_obj.write(self.logs.getvalue())
|
||||
file_obj.close()
|
||||
# 写入已执行步数
|
||||
with open("Data/Task_" + str(self.id) + "/" + self.saveName + '_steps.txt', 'w', encoding='utf-8-sig') as file_obj:
|
||||
with open("Data/Task_" + str(self.id) + "/" + self.saveName + '_steps.txt', 'w',
|
||||
encoding='utf-8-sig') as file_obj:
|
||||
file_obj.write(str(self.totalSteps + 1))
|
||||
file_obj.close()
|
||||
# 写入数据
|
||||
@ -629,7 +647,11 @@ class BrowserThread(Thread):
|
||||
self.recordLog("跳过本次循环|Skip this loop")
|
||||
elif codeMode == 7: # 暂停程序执行
|
||||
self.event.clear()
|
||||
self.print_and_log("任务已暂停,长按p键继续执行...|Task paused, long press 'p' to continue...")
|
||||
self.print_and_log(
|
||||
f"根据设置的自定义操作,任务已暂停,长按{self.service['pauseKey']}键继续执行...|Task paused according to custom operation, long press '{self.service['pauseKey']}' to continue...")
|
||||
elif codeMode == 8: # 刷新页面
|
||||
self.browser.refresh()
|
||||
self.print_and_log("根据设置的自定义操作,任务已刷新页面|Task refreshed page according to custom operation")
|
||||
else: # 0 1 5 6
|
||||
output = self.execute_code(
|
||||
codeMode, code, max_wait_time, iframe=paras["iframe"])
|
||||
@ -760,7 +782,7 @@ class BrowserThread(Thread):
|
||||
for i in node["sequence"]: # 从根节点开始向下读取
|
||||
self.executeNode(i, loopValue, loopPath, index)
|
||||
elif node["option"] == 1: # 打开网页操作
|
||||
if not (nodeId == 1 and self.service["cloudflare"] == 1):
|
||||
# if not (nodeId == 1 and self.service["cloudflare"] == 1):
|
||||
self.openPage(node["parameters"], loopValue)
|
||||
elif node["option"] == 2: # 点击元素
|
||||
self.clickElement(node["parameters"], loopValue, loopPath, index)
|
||||
@ -856,16 +878,20 @@ class BrowserThread(Thread):
|
||||
elif tType <= 8: # JS命令返回值
|
||||
if tType == 5: # JS命令返回值等于
|
||||
output = self.execute_code(
|
||||
0, cnode["parameters"]["code"], cnode["parameters"]["waitTime"], iframe=cnode["parameters"]["iframe"])
|
||||
0, cnode["parameters"]["code"], cnode["parameters"]["waitTime"],
|
||||
iframe=cnode["parameters"]["iframe"])
|
||||
elif tType == 6: # System
|
||||
output = self.execute_code(
|
||||
1, cnode["parameters"]["code"], cnode["parameters"]["waitTime"], iframe=cnode["parameters"]["iframe"])
|
||||
1, cnode["parameters"]["code"], cnode["parameters"]["waitTime"],
|
||||
iframe=cnode["parameters"]["iframe"])
|
||||
elif tType == 7: # 针对当前循环项的JS命令返回值
|
||||
output = self.execute_code(
|
||||
2, cnode["parameters"]["code"], cnode["parameters"]["waitTime"], loopElement, iframe=cnode["parameters"]["iframe"])
|
||||
2, cnode["parameters"]["code"], cnode["parameters"]["waitTime"], loopElement,
|
||||
iframe=cnode["parameters"]["iframe"])
|
||||
elif tType == 8: # 针对当前循环项的System命令返回值
|
||||
output = self.execute_code(
|
||||
6, cnode["parameters"]["code"], cnode["parameters"]["waitTime"], loopElement, iframe=cnode["parameters"]["iframe"])
|
||||
6, cnode["parameters"]["code"], cnode["parameters"]["waitTime"], loopElement,
|
||||
iframe=cnode["parameters"]["iframe"])
|
||||
try:
|
||||
if output.find("rue") != -1: # 如果返回值中包含true
|
||||
code = 1
|
||||
@ -973,7 +999,9 @@ class BrowserThread(Thread):
|
||||
break
|
||||
if int(node["parameters"]["breakMode"]) > 0: # 如果设置了退出循环的脚本条件
|
||||
output = self.execute_code(int(
|
||||
node["parameters"]["breakMode"]) - 1, node["parameters"]["breakCode"], node["parameters"]["breakCodeWaitTime"], iframe=node["parameters"]["iframe"])
|
||||
node["parameters"]["breakMode"]) - 1, node["parameters"]["breakCode"],
|
||||
node["parameters"]["breakCodeWaitTime"],
|
||||
iframe=node["parameters"]["iframe"])
|
||||
code = get_output_code(output)
|
||||
if code <= 0:
|
||||
break
|
||||
@ -1051,7 +1079,9 @@ class BrowserThread(Thread):
|
||||
index -= 1 # 如果是data:开头的网址,就要重试一次
|
||||
if int(node["parameters"]["breakMode"]) > 0: # 如果设置了退出循环的脚本条件
|
||||
output = self.execute_code(int(
|
||||
node["parameters"]["breakMode"]) - 1, node["parameters"]["breakCode"], node["parameters"]["breakCodeWaitTime"], iframe=node["parameters"]["iframe"])
|
||||
node["parameters"]["breakMode"]) - 1, node["parameters"]["breakCode"],
|
||||
node["parameters"]["breakCodeWaitTime"],
|
||||
iframe=node["parameters"]["iframe"])
|
||||
code = get_output_code(output)
|
||||
if code <= 0:
|
||||
break
|
||||
@ -1149,7 +1179,9 @@ class BrowserThread(Thread):
|
||||
raise
|
||||
if int(node["parameters"]["breakMode"]) > 0: # 如果设置了退出循环的脚本条件
|
||||
output = self.execute_code(int(
|
||||
node["parameters"]["breakMode"]) - 1, node["parameters"]["breakCode"], node["parameters"]["breakCodeWaitTime"], iframe=node["parameters"]["iframe"])
|
||||
node["parameters"]["breakMode"]) - 1, node["parameters"]["breakCode"],
|
||||
node["parameters"]["breakCodeWaitTime"],
|
||||
iframe=node["parameters"]["iframe"])
|
||||
code = get_output_code(output)
|
||||
if code <= 0:
|
||||
break
|
||||
@ -1172,7 +1204,9 @@ class BrowserThread(Thread):
|
||||
break
|
||||
if int(node["parameters"]["breakMode"]) > 0: # 如果设置了退出循环的脚本条件
|
||||
output = self.execute_code(int(
|
||||
node["parameters"]["breakMode"]) - 1, node["parameters"]["breakCode"], node["parameters"]["breakCodeWaitTime"], iframe=node["parameters"]["iframe"])
|
||||
node["parameters"]["breakMode"]) - 1, node["parameters"]["breakCode"],
|
||||
node["parameters"]["breakCodeWaitTime"],
|
||||
iframe=node["parameters"]["iframe"])
|
||||
code = get_output_code(output)
|
||||
if code <= 0:
|
||||
break
|
||||
@ -1200,7 +1234,9 @@ class BrowserThread(Thread):
|
||||
break
|
||||
if int(node["parameters"]["breakMode"]) > 0: # 如果设置了退出循环的脚本条件
|
||||
output = self.execute_code(int(
|
||||
node["parameters"]["breakMode"]) - 1, node["parameters"]["breakCode"], node["parameters"]["breakCodeWaitTime"], iframe=node["parameters"]["iframe"])
|
||||
node["parameters"]["breakMode"]) - 1, node["parameters"]["breakCode"],
|
||||
node["parameters"]["breakCodeWaitTime"],
|
||||
iframe=node["parameters"]["iframe"])
|
||||
code = get_output_code(output)
|
||||
if code <= 0:
|
||||
break
|
||||
@ -1208,13 +1244,16 @@ class BrowserThread(Thread):
|
||||
while True: # do while循环
|
||||
if int(node["parameters"]["loopType"]) == 5: # JS
|
||||
output = self.execute_code(
|
||||
0, node["parameters"]["code"], node["parameters"]["waitTime"], iframe=node["parameters"]["iframe"])
|
||||
0, node["parameters"]["code"], node["parameters"]["waitTime"],
|
||||
iframe=node["parameters"]["iframe"])
|
||||
elif int(node["parameters"]["loopType"]) == 6: # System
|
||||
output = self.execute_code(
|
||||
1, node["parameters"]["code"], node["parameters"]["waitTime"], iframe=node["parameters"]["iframe"])
|
||||
1, node["parameters"]["code"], node["parameters"]["waitTime"],
|
||||
iframe=node["parameters"]["iframe"])
|
||||
elif int(node["parameters"]["loopType"]) == 7: # Python
|
||||
output = self.execute_code(
|
||||
6, node["parameters"]["code"], node["parameters"]["waitTime"], iframe=node["parameters"]["iframe"])
|
||||
6, node["parameters"]["code"], node["parameters"]["waitTime"],
|
||||
iframe=node["parameters"]["iframe"])
|
||||
code = get_output_code(output)
|
||||
if code <= 0:
|
||||
break
|
||||
@ -1414,6 +1453,22 @@ class BrowserThread(Thread):
|
||||
self.print_and_log("Failed to click element:" + path,
|
||||
", please try to change the click type to JavaScript Click.")
|
||||
self.print_and_log(e)
|
||||
|
||||
# 弹窗处理
|
||||
if para["alertHandleType"] > 0:
|
||||
try:
|
||||
time.sleep(1.5)
|
||||
alert = self.browser.switch_to.alert
|
||||
alertHandleType = int(para["alertHandleType"])
|
||||
if alertHandleType == 1:
|
||||
alert.accept()
|
||||
self.print_and_log("已点击确认|Clicked OK")
|
||||
elif alertHandleType == 2:
|
||||
alert.dismiss()
|
||||
self.print_and_log("已点击取消|Clicked Cancel")
|
||||
except Exception as e:
|
||||
self.print_and_log("找不到弹窗|Cannot find alert")
|
||||
|
||||
# 点击后对该元素执行一段JavaScript代码
|
||||
try:
|
||||
if para["afterJS"] != "":
|
||||
@ -1670,7 +1725,8 @@ class BrowserThread(Thread):
|
||||
# relativeXPath = lowercase_tags_in_xpath(relativeXPath)
|
||||
# 已经有text()或@href了,不需要再加
|
||||
content_type = ""
|
||||
if relativeXPath.find("/@href") >= 0 or relativeXPath.find("/text()") >= 0 or relativeXPath.find("::text()") >= 0:
|
||||
if relativeXPath.find("/@href") >= 0 or relativeXPath.find("/text()") >= 0 or relativeXPath.find(
|
||||
"::text()") >= 0:
|
||||
content_type = ""
|
||||
elif p["nodeType"] == 2:
|
||||
content_type = "//@href"
|
||||
@ -1721,20 +1777,26 @@ class BrowserThread(Thread):
|
||||
else:
|
||||
content = p["default"]
|
||||
if not self.dataNotFoundKeys[p["name"]]:
|
||||
self.print_and_log('Element %s not found with parameter name %s when extracting data, use default, this error will only show once' % (
|
||||
self.print_and_log(
|
||||
'Element %s not found with parameter name %s when extracting data, use default, this error will only show once' % (
|
||||
relativeXPath, p["name"]))
|
||||
self.print_and_log("提取数据操作时,字段名 %s 对应XPath %s 未找到,使用默认值,本字段将不再重复报错" % (
|
||||
self.print_and_log(
|
||||
"提取数据操作时,字段名 %s 对应XPath %s 未找到,使用默认值,本字段将不再重复报错" % (
|
||||
p["name"], relativeXPath))
|
||||
self.dataNotFoundKeys[p["name"]] = True
|
||||
except Exception as e:
|
||||
if not self.dataNotFoundKeys[p["name"]]:
|
||||
self.print_and_log('Element %s not found with parameter name %s when extracting data, use default, this error will only show once' % (
|
||||
self.print_and_log(
|
||||
'Element %s not found with parameter name %s when extracting data, use default, this error will only show once' % (
|
||||
relativeXPath, p["name"]))
|
||||
self.print_and_log("提取数据操作时,字段名 %s 对应XPath %s 未找到(请查看原因,如是否翻页太快页面元素未加载出来),使用默认值,本字段将不再重复报错" % (
|
||||
self.print_and_log(
|
||||
"提取数据操作时,字段名 %s 对应XPath %s 未找到(请查看原因,如是否翻页太快页面元素未加载出来),使用默认值,本字段将不再重复报错" % (
|
||||
p["name"], relativeXPath))
|
||||
self.dataNotFoundKeys[p["name"]] = True
|
||||
try:
|
||||
self.outputParameters[p["name"]] = content
|
||||
|
||||
except:
|
||||
self.outputParameters[p["name"]] = p["default"]
|
||||
# 对于不能优化的操作,使用selenium执行
|
||||
for p in para["paras"]:
|
||||
if not p["optimizable"]:
|
||||
@ -1765,7 +1827,8 @@ class BrowserThread(Thread):
|
||||
else:
|
||||
element = self.browser.find_element(
|
||||
By.XPATH, relativeXPath, iframe=p["iframe"])
|
||||
except (NoSuchElementException, InvalidSelectorException, StaleElementReferenceException): # 找不到元素的时候,使用默认值
|
||||
except (
|
||||
NoSuchElementException, InvalidSelectorException, StaleElementReferenceException): # 找不到元素的时候,使用默认值
|
||||
# self.print_and_log(p)
|
||||
try:
|
||||
content = p["default"]
|
||||
@ -1774,9 +1837,11 @@ class BrowserThread(Thread):
|
||||
self.outputParameters[p["name"]] = content
|
||||
try:
|
||||
if not self.dataNotFoundKeys[p["name"]]:
|
||||
self.print_and_log('Element %s not found with parameter name %s when extracting data, use default, this error will only show once' % (
|
||||
self.print_and_log(
|
||||
'Element %s not found with parameter name %s when extracting data, use default, this error will only show once' % (
|
||||
relativeXPath, p["name"]))
|
||||
self.print_and_log("提取数据操作时,字段名 %s 对应XPath %s 未找到,使用默认值,本字段将不再重复报错" % (
|
||||
self.print_and_log(
|
||||
"提取数据操作时,字段名 %s 对应XPath %s 未找到,使用默认值,本字段将不再重复报错" % (
|
||||
p["name"], relativeXPath))
|
||||
except:
|
||||
pass
|
||||
@ -1841,6 +1906,7 @@ class BrowserThread(Thread):
|
||||
|
||||
if __name__ == '__main__':
|
||||
from multiprocessing import freeze_support
|
||||
|
||||
freeze_support() # 防止无限死循环多开
|
||||
|
||||
# 如果需要调试程序,请在命令行参数中加入--keyboard 0 来禁用键盘监听以提升调试速度
|
||||
@ -1862,10 +1928,11 @@ if __name__ == '__main__':
|
||||
options = Options()
|
||||
driver_path = "chromedriver.exe"
|
||||
import platform
|
||||
|
||||
print(sys.platform, platform.architecture())
|
||||
option = webdriver.ChromeOptions()
|
||||
if not os.path.exists(os.getcwd()+"/Data"):
|
||||
os.mkdir(os.getcwd()+"/Data")
|
||||
if not os.path.exists(os.getcwd() + "/Data"):
|
||||
os.mkdir(os.getcwd() + "/Data")
|
||||
if sys.platform == "darwin" and platform.architecture()[0] == "64bit":
|
||||
options.binary_location = "EasySpider.app/Contents/Resources/app/chrome_mac64.app/Contents/MacOS/Google Chrome"
|
||||
# MacOS需要用option而不是options!
|
||||
@ -1884,9 +1951,9 @@ if __name__ == '__main__':
|
||||
c.config_folder = os.path.expanduser(
|
||||
"~/Library/Application Support/EasySpider/")
|
||||
# print("Config folder for MacOS:", c.config_folder)
|
||||
elif os.path.exists(os.getcwd()+"/EasySpider/resources"): # 打包后的路径
|
||||
elif os.path.exists(os.getcwd() + "/EasySpider/resources"): # 打包后的路径
|
||||
print("Finding chromedriver in EasySpider",
|
||||
os.getcwd()+"/EasySpider")
|
||||
os.getcwd() + "/EasySpider")
|
||||
if sys.platform == "win32" and platform.architecture()[0] == "32bit":
|
||||
options.binary_location = os.path.join(
|
||||
os.getcwd(), "EasySpider/resources/app/chrome_win32/chrome.exe") # 指定chrome位置
|
||||
@ -1922,10 +1989,10 @@ if __name__ == '__main__':
|
||||
# options.binary_location = "./Chrome/chrome.exe" # 指定chrome位置
|
||||
# # option.binary_location = "C:\\Users\\q9823\\AppData\\Local\\Google\\Chrome\\Application\\chrome.exe"
|
||||
# driver_path = "./Chrome/chromedriver.exe"
|
||||
elif os.path.exists(os.getcwd()+"/../ElectronJS"):
|
||||
elif os.path.exists(os.getcwd() + "/../ElectronJS"):
|
||||
# 软件dev用
|
||||
print("Finding chromedriver in EasySpider",
|
||||
os.getcwd()+"/ElectronJS")
|
||||
os.getcwd() + "/ElectronJS")
|
||||
option.binary_location = "../ElectronJS/chrome_win64/chrome.exe" # 指定chrome位置
|
||||
options.binary_location = "../ElectronJS/chrome_win64/chrome.exe" # 指定chrome位置
|
||||
driver_path = "../ElectronJS/chrome_win64/chromedriver_win64.exe"
|
||||
@ -1965,10 +2032,14 @@ if __name__ == '__main__':
|
||||
options.add_argument(
|
||||
f'--user-data-dir={absolute_user_data_folder}') # TMALL 反扒
|
||||
options.add_argument("--profile-directory=Default")
|
||||
print("正在使用带用户信息浏览器模式,注意此模式同一个用户信息目录只能同时运行一个浏览器实例,如果需要多开请复制用户信息目录并载入复制的目录地址,具体请参考程序多开文档:https://github.com/NaiboWang/EasySpider/wiki/Run-multiple-tasks-in-parallel")
|
||||
print("Using browser with user data, please note that only one browser instance can be run at the same time with the same user data directory, if you need to run multiple instances, please copy the user data directory and load the copied directory address, please refer to the program multiple open document for details: https://github.com/NaiboWang/EasySpider/wiki/Run-multiple-tasks-in-parallel")
|
||||
print("如果报错Selenium.common.exceptions.WebDriverException: Message: unknown error: Chrome failed to start: exited abnormally,说明有之前运行的Chrome实例没有正常关闭,请关闭之前打开的所有Chrome实例后再运行程序即可。")
|
||||
print("If you get an error Selenium.common.exceptions.WebDriverException: Message: unknown error: Chrome failed to start: exited abnormally, it means that there is a Chrome instance that was not closed properly before, please close all Chrome instances that were opened before running the program.")
|
||||
print(
|
||||
"正在使用带用户信息浏览器模式,注意此模式同一个用户信息目录只能同时运行一个浏览器实例,如果需要多开请复制用户信息目录并载入复制的目录地址,具体请参考程序多开文档:https://github.com/NaiboWang/EasySpider/wiki/Run-multiple-tasks-in-parallel")
|
||||
print(
|
||||
"Using browser with user data, please note that only one browser instance can be run at the same time with the same user data directory, if you need to run multiple instances, please copy the user data directory and load the copied directory address, please refer to the program multiple open document for details: https://github.com/NaiboWang/EasySpider/wiki/Run-multiple-tasks-in-parallel")
|
||||
print(
|
||||
"如果报错Selenium.common.exceptions.WebDriverException: Message: unknown error: Chrome failed to start: exited abnormally,说明有之前运行的Chrome实例没有正常关闭,请关闭之前打开的所有Chrome实例后再运行程序即可。")
|
||||
print(
|
||||
"If you get an error Selenium.common.exceptions.WebDriverException: Message: unknown error: Chrome failed to start: exited abnormally, it means that there is a Chrome instance that was not closed properly before, please close all Chrome instances that were opened before running the program.")
|
||||
|
||||
if c.headless:
|
||||
print("Headless mode")
|
||||
@ -2002,8 +2073,14 @@ if __name__ == '__main__':
|
||||
with open("execution_instances/" + str(i) + ".json", 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
service = json.loads(content) # 加载服务信息
|
||||
try:
|
||||
print("Task Name:", service["name"])
|
||||
print("任务名称:", service["name"])
|
||||
except:
|
||||
print("Cannot find task with id:" + str(i) + ", please check whether " + str(i) +
|
||||
".json exists in the 'execution_instances' folder.")
|
||||
print("未找到id为" + str(i) + "的任务,请检查'execution_instances'文件夹中是否存在" + str(i) + ".json文件。")
|
||||
continue
|
||||
try:
|
||||
cloudflare = service["cloudflare"]
|
||||
except:
|
||||
@ -2080,19 +2157,19 @@ if __name__ == '__main__':
|
||||
# Set the pause operation
|
||||
# if sys.platform != "linux":
|
||||
# time.sleep(3)
|
||||
# print("\n\n----------------------------------")
|
||||
# print("正在运行任务,长按键盘p键可暂停任务的执行以便手工操作浏览器如输入验证码;如果想恢复任务的执行,请再次长按p键。")
|
||||
# print("Running task, long press 'p' to pause the task for manual operation of the browser such as entering the verification code; If you want to resume the execution of the task, please long press 'p' again.")
|
||||
# print("----------------------------------\n\n")
|
||||
# Thread(target=check_pause, args=("p", event)).start()
|
||||
# else:
|
||||
time.sleep(3)
|
||||
press_time = {"duration": 0, "is_pressed": False}
|
||||
try:
|
||||
pause_key = service["pauseKey"]
|
||||
except:
|
||||
pause_key = "p"
|
||||
press_time = {"duration": 0, "is_pressed": False, "pause_key": pause_key}
|
||||
print("\n\n----------------------------------")
|
||||
print(
|
||||
"正在运行任务,长按键盘p键可暂停任务的执行以便手工操作浏览器如输入验证码;如果想恢复任务的执行,请再次长按p键。")
|
||||
"正在运行任务,长按键盘" + pause_key + "键可暂停任务的执行以便手工操作浏览器如输入验证码;如果想恢复任务的执行,请再次长按" + pause_key + "键。")
|
||||
print(
|
||||
"Running task, long press 'p' to pause the task for manual operation of the browser such as entering the verification code; If you want to resume the execution of the task, please long press 'p' again.")
|
||||
"Running task, long press '" + pause_key + "' to pause the task for manual operation of the browser such as entering the verification code; If you want to resume the execution of the task, please long press '" + pause_key + "' again.")
|
||||
print("----------------------------------\n\n")
|
||||
# if cloudflare:
|
||||
# print("过Cloudflare验证模式有时候会不稳定,如果无法通过验证则需要隔几分钟重试一次,或者可以更换新的用户信息文件夹再执行任务。")
|
||||
@ -2100,7 +2177,8 @@ if __name__ == '__main__':
|
||||
# 使用监听器监听键盘输入
|
||||
try:
|
||||
if c.keyboard:
|
||||
with Listener(on_press=on_press_creator(press_time, event), on_release=on_release_creator(event, press_time)) as listener:
|
||||
with Listener(on_press=on_press_creator(press_time, event),
|
||||
on_release=on_release_creator(event, press_time)) as listener:
|
||||
listener.join()
|
||||
except:
|
||||
pass
|
||||
|
@ -31,7 +31,7 @@ def lowercase_tags_in_xpath(xpath):
|
||||
def on_press_creator(press_time, event):
|
||||
def on_press(key):
|
||||
try:
|
||||
if key.char == 'p':
|
||||
if key.char == press_time["pause_key"]:
|
||||
if press_time["is_pressed"] == False: # 没按下p键时,记录按下p键的时间
|
||||
press_time["duration"] = time.time()
|
||||
press_time["is_pressed"] = True
|
||||
@ -39,14 +39,14 @@ def on_press_creator(press_time, event):
|
||||
duration = time.time() - press_time["duration"]
|
||||
if duration > 2:
|
||||
if event._flag == False:
|
||||
print("任务执行中,长按p键暂停执行。")
|
||||
print("Task is running, long press 'p' to pause.")
|
||||
print("任务执行中,长按" + press_time["pause_key"] + "键暂停执行。")
|
||||
print("Task is running, long press '" + press_time["pause_key"] + "' to pause.")
|
||||
# 设置Event的值为True,使得线程b可以继续执行
|
||||
event.set()
|
||||
else:
|
||||
# 设置Event的值为False,使得线程b暂停执行
|
||||
print("任务已暂停,长按p键继续执行...")
|
||||
print("Task paused, long press 'p' to continue...")
|
||||
print("任务已暂停,长按" + press_time["pause_key"] + "键继续执行...")
|
||||
print("Task paused, long press '" + press_time["pause_key"] + "' to continue...")
|
||||
event.clear()
|
||||
press_time["duration"] = time.time()
|
||||
press_time["is_pressed"] = False
|
||||
@ -177,21 +177,19 @@ def write_to_csv(file_name, data, record):
|
||||
f.close()
|
||||
|
||||
|
||||
def eval_repl(matchobj):
|
||||
print(matchobj.group(1))
|
||||
return str(eval(matchobj.group(1), globals(), locals()))
|
||||
|
||||
|
||||
|
||||
def replace_field_values(orginal_text, outputParameters, browser=None):
|
||||
pattern = r'Field\["([^"]+)"\]'
|
||||
try:
|
||||
replaced_text = re.sub(
|
||||
pattern, lambda match: outputParameters.get(match.group(1), ''), orginal_text)
|
||||
if re.search(r'eval(', replaced_text, re.IGNORECASE): # 如果返回值中包含EVAL
|
||||
if re.search(r'eval\(', replaced_text, re.IGNORECASE): # 如果返回值中包含EVAL
|
||||
replaced_text = replaced_text.replace("self.", "browser.")
|
||||
replaced_text = re.sub(r'eval\("(.*?)"\)', lambda match: str(eval(match.group(1))), replaced_text, flags=re.IGNORECASE)
|
||||
except:
|
||||
pattern = re.compile(r'(?i)eval\("([^"]+)"\)')
|
||||
match = pattern.search(replaced_text)
|
||||
eval_replaced_text = str(eval(match.group(1)))
|
||||
replaced_text = replaced_text.replace(match.group(0), eval_replaced_text)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
replaced_text = orginal_text
|
||||
return replaced_text
|
||||
|
||||
|
@ -18,6 +18,7 @@
|
||||
<p style="color:black; margin-top: 10px">● 鼠标移动到笑脸☺查看操作提示。</p>
|
||||
<p style="color:black; margin-top: 10px">● 鼠标移动到元素上后,请<strong>右键</strong>点击或者按<strong>F7</strong>键选中页面元素。
|
||||
</p>
|
||||
<p style="color:black; margin-top: 10px">● 如果此操作台把页面元素挡住了,可以点击此操作台右下角的×按钮键关闭操作台。</p>
|
||||
<p style="color:black; margin-top: 10px">● 通过鼠标左键进行点击时,页面也会有反应,但左键点击发生的操作不会被记录在任务流程中;同理,如果想输入文本框但并不想将动作记录,可以鼠标移动到文本框,并按键盘的<strong>F9</strong>进行输入。
|
||||
</p>
|
||||
<p style="color:black; margin-top: 10px">● 如果不小心左键点选了元素导致页面跳转,直接后退或者切换回标签页即可。</p>
|
||||
@ -172,6 +173,8 @@
|
||||
<p style="color:black">● Mouse move to smiling face ☺ to see operation help.</p>
|
||||
<p style="color:black">● When your mouse moves to the element, please <strong>right-click</strong> your
|
||||
mouse button or press <strong>F7</strong> on the keyboard to select it.</p>
|
||||
<p style="color:black">● If this toolbox blocks the page element, you can click the × button in the
|
||||
lower right corner of this toolbox to close it.</p>
|
||||
<p style="color:black; margin-top: 10px">● When clicked with the left mouse button, the page will also respond, but this click operation will not be recorded in the task flow. Similarly, if you want to input in a text box but do not want the action to be recorded , you can move the mouse to the text box and press <strong>F9</strong> on the keyboard to input.</p>
|
||||
<p style="color:black; margin-top: 10px">● If you accidentally left-click on an element and cause the page to jump, simply go back or switch back to the tab.</p>
|
||||
<p style="color:black; margin-top: 10px">● After the operation is completed, such as if no "Collect Data" operation is added in the task flowchart after clicking "Confirm Collect", just <strong> retry </strong> again.</p>
|
||||
|
Loading…
x
Reference in New Issue
Block a user