浏览代码

Navigation about Next Page!!!

naibo 1 年之前
父节点
当前提交
be2f980734

二进制
ElectronJS/EasySpider_en.crx


二进制
ElectronJS/EasySpider_zh.crx


+ 10 - 2
ElectronJS/main.js

@@ -470,8 +470,16 @@ async function beginInvoke(msg, ws) {
                     try {
                         await driver.get(url);
                     } catch (e) {
-                        driver.switchTo().window(current_handle);
-                        await driver.get(url);
+                        try {
+                            await driver.switchTo().window(current_handle);
+                            await driver.get(url);
+                        } catch (e){
+                            let all_handles = await driver.getAllWindowHandles();
+                            let handle = all_handles[all_handles.length - 1];
+                            await driver.switchTo().window(handle);
+                            await driver.get(url);
+                        }
+
                     }
                 } else if (option == 2 || option == 7) { //点击事件
                     let elementInfo = {"iframe": parameters.iframe, "xpath": parameters.xpath, "id": -1};

+ 5 - 2
ElectronJS/src/taskGrid/FlowChart.html

@@ -698,6 +698,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>Data Write Mode (The export file/database table name above must be fixed, effective when the same task ID is executed multiple times):</label>
+                    <select id="dataWriteMode" name="dataWriteMode" class="form-control">
+                        <option value="1">Append (If the file exists, append to it)</option>
+                        <option value="2">Overwrite (If the file exists, overwrite it)</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>-->
@@ -722,8 +727,6 @@ If the expression returns a value greater than 0 or evaluates to True, the opera
                         <option value="0">No</option>
                         <option value="1">Yes (Requires running the same task ID and the same file name, please execute from the command line and specify the ID)</option>
                     </select>
-<!--                    <label>Wait time for the browser to close after the task is executed (in seconds):</label>-->
-                    <label>任务执行完毕后自动关闭浏览器等待秒数(用户临时目录将在浏览器关闭后自动删除):</label>
                     <label>Wait time for the browser to close after the task is executed (in seconds), the temporary user data directory will be automatically deleted after the browser is closed:</label>
                     <input onkeydown="inputDelete(event)" type="number" value="60" id="quitWaitTime" name="quitWaitTime" class="form-control"></input>
                     <label>Maximum Display Length of Data in Console Preview:</label>

+ 6 - 3
ElectronJS/src/taskGrid/FlowChart.js

@@ -436,7 +436,7 @@ function operationChange(e, theNode) {
     vueData.nowNodeIndex = actionSequence[theNode.getAttribute("data")];
     theNode.style.borderColor = "blue";
     handleElement(); //处理元素
-    if(debuggable){
+    if (debuggable) {
         trailElement(app._data.nowNode, 0);
     } else {
         debuggable = true;
@@ -504,7 +504,8 @@ function toolBoxKernel(e, para = null) {
         // let tarrow = DeepClone(app.$data.nowArrow);
         // refresh();
         // app._data.nowArrow =tarrow;
-    } else if (option == 11) { //复制操作
+    }
+    else if (option == 11) { //复制操作
         if (nowNode == null) {
             e.stopPropagation(); //防止冒泡
         } else if (nowNode.getAttribute("dataType") > 0) {
@@ -568,7 +569,9 @@ function toolBoxKernel(e, para = null) {
             } else {
                 showError(LANG("自己不能移动到自己的节点里!", "Cannot move inside self!"));
             }
-            e.stopPropagation(); //防止冒泡
+            if (e != null) {
+                e.stopPropagation(); //防止冒泡
+            }
         }
     }
     else if (option > 0) { //新增操作

+ 5 - 0
ElectronJS/src/taskGrid/FlowChart_CN.html

@@ -698,6 +698,11 @@ print(emotlib.emoji()) # 使用其中的函数。
                     </select>
                     <label>导出文件名/数据库表格名称(可使用../表示相对路径以改变文件保存位置,名称中的“current_time”会被替换为执行任务时的时间戳):</label>
                     <input onkeydown="inputDelete(event)" value="current_time" id="saveName" class="form-control"></input>
+                    <label>数据写入模式(上方导出文件名/数据库表格名称需固定,同一个任务ID多次执行时生效):</label>
+                    <select id="dataWriteMode" name="dataWriteMode" class="form-control">
+                        <option value=1>追加写入(如果文件已存在则在原文件后面追加)</option>
+                        <option value=2>覆盖写入(如果文件已存在则覆盖原文件)</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>-->

+ 30 - 0
ElectronJS/src/taskGrid/logic.js

@@ -105,6 +105,35 @@ function handleAddElement(msg) {
         addElement(8, msg);
         msg["xpath"] = ""; //循环点击每个元素,单个元素的xpath设置为空
         addElement(2, msg);
+    } else if (msg["type"] == "loopClickNextPage") {
+        let withLoop = msg.lastAction.includes("WithLoop"); //最后一个操作是否是循环操作
+        if (withLoop) {
+            //如果之前是循环提取数据操作,锚点先调整到循环提取数据操作下方
+            let loopElementId = app._data.nowArrow["pId"];
+            let loopElement = $("#" + loopElementId);
+            app._data.nowArrow["position"] = loopElement.attr("position");
+            app._data.nowArrow["pId"] = loopElement.attr("pId");
+        }
+        //按照循环点击元素的方式添加操作
+        addElement(8, msg);
+        msg["xpath"] = ""; //循环点击下一页,单个元素的xpath设置为空
+        addElement(2, msg);
+        //parentId是在actionSequence中的位置,nodeList[actionSequence[parentId]]才是父元素
+        //position是在父元素的sequence中的位置,nodeList[actionSequence[parentId]]["sequence"][position]才是该元素在nodeList中的位置,actionSequence[id]
+        let parentNode = nodeList[actionSequence[app._data.nowNode["parentId"]]]; //获取当前操作,即点击下一页操作的父元素,即循环操作
+        let parent_position = parentNode["position"];
+        let last_collect_data_node_position = parent_position - 1; //循环操作的上一个操作一般是提取数据操作
+        let parent_parentId = parentNode["parentId"]; //循环操作的父元素的id
+        let last_collect_data_node_index = nodeList[actionSequence[parent_parentId]]["sequence"][last_collect_data_node_position]; //提取数据操作在nodeList中的位置
+        let last_collect_data_node = nodeList[last_collect_data_node_index]; //提取数据操作
+        $("#" + last_collect_data_node["id"]).click(); //点击提取数据元素
+        app._data.nowArrow['position'] = -1; //循环点击下一页,下一个要插入的位置一般在元素上方
+        option = 10; //剪切操作
+        toolBoxKernel(null, null); //剪切操作
+        //切换到循环点击下一页操作
+        parentNode = nodeList[actionSequence[app._data.nowNode["parentId"]]]; //获取当前操作,即点击下一页操作的父元素,即循环操作
+        let parentId = parentNode["id"]; //循环操作的id
+        $("#" + parentId).click(); //点击循环操作
     } else if (msg["type"] == "singleCollect" || msg["type"] == "multiCollectNoPattern") {
         if (app._data.nowNode != null && app._data["nowNode"]["option"] == 3) { //如果现在节点就是提取数据节点,直接在此节点添加参数,而不是生成一个新的提取数据节点
             for (let i = 0; i < msg["parameters"].length; i++) {
@@ -552,6 +581,7 @@ function saveService(type) {
         "recordLog": parseInt($("#recordLog").val()),
         "outputFormat": $("#outputFormat").val(),
         "saveName": $("#saveName").val(),
+        "dataWriteMode": parseInt($("#dataWriteMode").val()),
         "inputExcel": $("#inputExcel").val(),
         "startFromExit": parseInt($("#startFromExit").val()),
         "pauseKey": $("#pauseKey").val(),

文件差异内容过多而无法显示
+ 0 - 0
ElectronJS/tasks/260.json


文件差异内容过多而无法显示
+ 0 - 0
ElectronJS/tasks/273.json


文件差异内容过多而无法显示
+ 0 - 0
ElectronJS/tasks/274.json


文件差异内容过多而无法显示
+ 0 - 0
ElectronJS/tasks/275.json


+ 1 - 1
ExecuteStage/.vscode/launch.json

@@ -12,7 +12,7 @@
             "justMyCode": false,
             //  "args": ["--ids", "[7]", "--read_type", "remote", "--headless", "0"]
             // "args": ["--ids", "[9]", "--read_type", "remote", "--headless", "0", "--saved_file_name", "YOUTUBE"]
-            "args": ["--ids", "[38]", "--headless", "0", "--user_data", "0", "--keyboard", "0",
+            "args": ["--ids", "[47]", "--headless", "0", "--user_data", "0", "--keyboard", "0",
         "--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"
         }

+ 8 - 1
ExecuteStage/easyspider_executestage.py

@@ -175,6 +175,13 @@ class BrowserThread(Thread):
         except:
             self.links = list(filter(isnotnull, service["url"]))  # 要执行的link
         self.OUTPUT = []  # 采集的数据
+        try:
+            self.dataWriteMode = service["dataWriteMode"] # 数据写入模式,1为追加,2为覆盖
+        except:
+            self.dataWriteMode = 1
+        if self.outputFormat == "csv" or self.outputFormat == "txt" or self.outputFormat == "xlsx" or self.outputFormat == "json":
+            if self.dataWriteMode == 2 and os.path.exists("Data/Task_" + str(self.id) + "/" + self.saveName + '.' + self.outputFormat):
+                os.remove("Data/Task_" + str(self.id) + "/" + self.saveName + '.' + self.outputFormat)
         self.writeMode = 1  # 写入模式,0为新建,1为追加
         if self.outputFormat == "csv" or self.outputFormat == "txt" or self.outputFormat == "xlsx":
             if not os.path.exists("Data/Task_" + str(self.id) + "/" + self.saveName + '.' + self.outputFormat):
@@ -184,7 +191,7 @@ class BrowserThread(Thread):
             self.writeMode = 3  # JSON模式无需判断是否存在文件
         elif self.outputFormat == "mysql":
             self.mysql = myMySQL(config["mysql_config_path"])
-            self.mysql.create_table(self.saveName, service["outputParameters"])
+            self.mysql.create_table(self.saveName, service["outputParameters"], remove_if_exists=self.dataWriteMode == 2)
             self.writeMode = 2
         if self.writeMode == 0:
             self.print_and_log("新建模式|Create Mode")

+ 8 - 3
ExecuteStage/utils.py

@@ -417,13 +417,18 @@ class myMySQL:
                 "Failed to connect to the database, please check if the configuration file is correct.")
             sys.exit()
 
-    def create_table(self, table_name, parameters):
+    def create_table(self, table_name, parameters, remove_if_exists=False):
         self.table_name = table_name
         self.field_sql = "("
         self.cursor = self.conn.cursor()
         # 检查表是否存在
         self.cursor.execute(f"SHOW TABLES LIKE '{table_name}'")
         result = self.cursor.fetchone()
+        # 如果表存在,删除它
+        if result and remove_if_exists:
+            self.cursor.execute(f"DROP TABLE {table_name}")
+            result = None
+            print(f'数据表 {table_name} 已存在,已删除。')
 
         sql = "CREATE TABLE " + table_name + \
             " (_id INT AUTO_INCREMENT PRIMARY KEY, "
@@ -510,8 +515,8 @@ class myMySQL:
             for i in range(len(line)):
                 if record[i]:
                     to_write.append(line[i])
-            # 构造插入数据的 SQL 语句
-            sql = f'INSERT INTO {self.table_name} {self.field_sql} VALUES ('
+            # 构造插入数据的 SQL 语句, IGNORE表示如果主键重复则忽略
+            sql = f'INSERT IGNORE INTO {self.table_name} {self.field_sql} VALUES ('
             for _ in to_write:
                 sql += "%s, "
             # 移除最后的逗号并添加闭合的括号

+ 2 - 1
Extension/manifest_v3/src/content-scripts/global.js

@@ -239,6 +239,7 @@ export function clearEl(trail=false) {
     global.nodeList.splice(0, global.nodeList.length); //清空数组
     global.app._data.option = 0; //选项重置
     global.app._data.page = 0; //恢复原始页面
+    // global.app._data.nextPage = 0; //不出现翻页操作提示
 }
 
 
@@ -271,6 +272,7 @@ export function clearParameters(deal = true) //清空参数列表
     if (deal) //是否取消对选中的子元素进行处理
     {
         global.app._data.selectedDescendents = false;
+        global.app._data.selectStatus = false;
     }
     for (let o of global.outputParameterNodes) {
         o["node"].style.boxShadow = o["boxShadow"];
@@ -278,7 +280,6 @@ export function clearParameters(deal = true) //清空参数列表
     global.outputParameterNodes.splice(0);
     global.outputParameters.splice(0); //清空原来的参数列表
     global.app._data.valTable = []; //清空展现数组
-    global.app._data.selectStatus = false;
 }
 
 export function LANG(zh, en) {

+ 5 - 1
Extension/manifest_v3/src/content-scripts/messageInteraction.js

@@ -258,7 +258,7 @@ export function collectMultiWithPattern() {
 }
 
 //循环点击单个元素
-export function sendLoopClickSingle(name) {
+export function sendLoopClickSingle(name="") {
     let message = {
         "type": "loopClickSingle",
         "id": global.id,
@@ -271,9 +271,13 @@ export function sendLoopClickSingle(name) {
         "allXPaths": getElementXPaths(global.nodeList[0]["node"]),
         "loopType": 0, //循环类型,0为单个元素
         "nextPage": false, //是否循环点击下一页
+        "lastAction": global.app._data.lastAction,
     };
     if (name == "下一页元素") {
         message.nextPage = true;
+    } else if(name == "nextPageFromIndexPage") {
+        message.nextPage = true;
+        message.type = "loopClickNextPage";
     }
     let message_action = {
         type: 3, //消息类型,3代表元素增加事件

+ 188 - 111
Extension/manifest_v3/src/content-scripts/toolkit.vue

@@ -9,15 +9,19 @@
       <div class="realcontent">
         <div v-if="page==0">
           <div v-if="list.nl.length==0" :style="{overflow: 'auto', maxHeight: winHeight * 0.4 + 'px'}">
-            <input style="width:15px;height:15px;vertical-align:middle;" type="checkbox"
-                   v-on:mousedown="specialSelect"/>
-            <p style="margin-bottom:10px;display:inline-block">特殊点选模式<span
-                title="普通模式下如果不能选中元素可以勾选此项">☺</span></p>
+            <!--            <input style="width:15px;height:15px;vertical-align:middle;" type="checkbox"-->
+            <!--                   v-on:mousedown="specialSelect"/>-->
+            <!--            <p style="margin-bottom:10px;display:inline-block">特殊点选模式<span-->
+            <!--                title="普通模式下如果不能选中元素可以勾选此项">☺</span></p>-->
+            <p style="color: darkviolet;" v-if="nextPage==1">
+              ● 检测到您刚刚进行了采集数据的操作,如需设置翻页操作,请点击下方设置翻页操作选项。</p>
             <div class="innercontent" v-if="list.nl.length==0">
+              <div v-if="nextPage==1" class="nextPage">
+                <a v-on:mousedown="setNextPage">设置翻页操作</a><span title="可根据提示指定翻页按钮">☺</span></div>
               <div><a v-on:mousedown="getCurrentTitle">采集当前页面的标题</a><span title="当前页面标题">☺</span></div>
               <div><a v-on:mousedown="getCurrentURL">采集当前页面的网址</a><span title="当前页面URL地址">☺</span></div>
             </div>
-            <p style="color:black; margin-top: 10px">● 鼠标移动到笑脸☺查看操作提示。</p>
+            <p style="color:black; margin-top: 10px">● 鼠标移动到笑脸<span style="font-size: 20px"></span>查看操作提示。</p>
             <p style="color:black; margin-top: 10px">●
               鼠标移动到元素上后,请<strong>右键</strong>点击或者按<strong>F7</strong>键选中页面元素。
             </p>
@@ -147,7 +151,7 @@
             </div>
           </div>
 
-          <div v-if="valTable.length==0&&tname()!='下一页元素'"></div>
+          <!--          <div v-if="valTable.length==0&&tname()!='下一页元素'"></div>-->
 
           <div v-if="list.nl.length>0"
                style="bottom:12px;position:absolute;color:black!important;left:17px;font-size:13px">
@@ -158,7 +162,7 @@
             <p style="margin-left:16px;margin-bottom:0px">{{ lastElementXPath() }}</p>
           </div>
         </div>
-        <div v-if="page==1">
+        <div v-else-if="page==1">
           ● 请输入文字:
           <input id="WTextBox" v-model="text" autoFocus="autofocus" type="text"></input>
           <button style="margin-left:0px!important;" v-on:click="getInput">确定</button>
@@ -167,7 +171,7 @@
             输入&lt;enter&gt;或&lt;ENTER&gt;表示输入完成后模拟按下回车键,适用于只能通过回车键获得数据的情况。
           </div>
         </div>
-        <div v-if="page==2">
+        <div v-else-if="page==2">
           <span style="font-size: 15px"> ● 切换模式 </span>
           <select v-model="optionMode" @change="handleSelectChange">
             <option value=0>切换到下一个选项</option>
@@ -185,6 +189,21 @@
             <button style="margin-left:0px!important;" v-on:click="cancelInput">取消</button>
           </div>
         </div>
+        <div v-else-if="page==3">
+          <span
+              style="font-size: 15px"> ● 请在页面上右键选择要点击的下一页按钮/链接,如要取消设置翻页操作,请点击下方取消选项。</span>
+          <div style="font-size: 15px" v-if="list.nl.length==1">
+            ● 已选中一个元素,您可以点击下方选项确认设置翻页操作。
+          </div>
+          <div style="font-size: 15px; color: #c82333;" v-else-if="list.nl.length>1">
+            ● 翻页操作只能设置一个元素,请点击下方取消翻页操作选项并重新选择。
+          </div>
+          <div class="innercontent">
+            <div><a href="#" v-on:mousedown="confirmNextPage" v-if="list.nl.length==1" style="margin-bottom: 5px">确认设置翻页操作</a>
+            </div>
+            <div><a href="#" v-on:mousedown="cancelNextPage">取消设置翻页操作</a></div>
+          </div>
+        </div>
       </div>
     </div>
     <div v-else-if="lang=='en'">
@@ -192,17 +211,23 @@
       <div class="realcontent">
         <div v-if="page==0">
           <div v-if="list.nl.length==0" :style="{overflow: 'auto', maxHeight: winHeight * 0.4 + 'px'}">
-            <input style="width:15px;height:15px;vertical-align:middle;" type="checkbox"
-                   v-on:mousedown="specialSelect"> </input>
-            <p style="margin-bottom:10px;display:inline-block">Special click mode<span
-                title="If cannot select element by mouse, select this option">☺</span></p>
+            <!--            <input style="width:15px;height:15px;vertical-align:middle;" type="checkbox"-->
+            <!--                   v-on:mousedown="specialSelect"> </input>-->
+            <!--            <p style="margin-bottom:10px;display:inline-block">Special click mode<span-->
+            <!--                title="If cannot select element by mouse, select this option">☺</span></p>-->
+            <p style="color: darkviolet;" v-if="nextPage==1">
+              ● A data collection operation has just been detected. If you need to configure pagination, please click
+              the option below to set up pagination.</p>
             <div class="innercontent" v-if="list.nl.length==0">
+              <div v-if="nextPage==1" class="nextPage">
+                <a v-on:mousedown="setNextPage">Set up pagination</a><span
+                  title="Follow the prompt to specify the paging button">☺</span></div>
               <div><a v-on:mousedown="getCurrentTitle">Collect Title of current page</a><span
                   title="Title of this page">☺</span></div>
               <div><a v-on:mousedown="getCurrentURL">Collect URL of current page</a><span
                   title="URL of this page">☺</span></div>
             </div>
-            <p style="color:black; margin-top: 10px">● Mouse move to smiling face ☺ to see operation help.</p>
+            <p style="color:black; margin-top: 10px">● Mouse move to smiling face <span style="font-size: 20px"></span> to see operation help.</p>
             <p style="color:black; margin-top: 10px">● 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>
@@ -211,7 +236,8 @@
               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
+              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>
@@ -236,7 +262,8 @@
                   <!-- <div v-if="tname()=='selection box'"> <a>循环切换该下拉项</a><span title="">☺</span></div> -->
                   <div v-if="tname()=='选择框'"><a v-on:mousedown="changeSelect">Change selection option</a><span
                       title=""></span></div>
-                  <div v-if="tname()=='文本框'"><a v-on:mousedown="setInput(false)">Input Text</a><span title=""></span>
+                  <div v-if="tname()=='文本框'"><a v-on:mousedown="setInput(false)">Input Text</a><span
+                      title=""></span>
                   </div>
                   <div v-if="tname()=='文本框'"><a v-on:mousedown="setInput(true)">Input Text (Batch)</a><span
                       title=""></span>
@@ -280,115 +307,129 @@
               </div>
             </div>
           </div>
-        </div>
 
-        <div v-if="list.nl.length>1">
+          <div v-if="list.nl.length>1">
 
-          <div v-if="option==100">
-            ● Already selected the following element, you can:
-            <div class="innercontent">
-              <div><a v-on:mousedown="confirmCollectMulti">Collect Data</a><span title=""></span></div>
-              <div><a v-on:mousedown="revoke">Revoke selection</a><span title=""></span></div>
+            <div v-if="option==100">
+              ● Already selected the following element, you can:
+              <div class="innercontent">
+                <div><a v-on:mousedown="confirmCollectMulti">Collect Data</a><span title=""></span></div>
+                <div><a v-on:mousedown="revoke">Revoke selection</a><span title=""></span></div>
+              </div>
             </div>
-          </div>
 
-          <div v-if="option!=100">
-            ● Already selected {{ numOfList() }} similar elements, <span
-              v-if="numOfReady()>0">and we find other{{ numOfReady() }} similar elements (If unsatisfied with auto-detected similar elements, you can continue to manually select the rest of the elements that you think are similar), </span>you
-            can:
-            <div class="innercontent">
-              <div v-if="numOfReady()>0"><a v-on:mousedown="selectAll">Select All</a><span title=""></span></div>
-              <div v-if="existDescendents()&&(tname()=='元素' || tname()=='链接')"><a
-                  v-on:mousedown="selectDescendents">Select child elements (Greedy)</a><span
-                  title="Select All child elements for all blocks">☺</span></div>
-              <div v-if="existDescendents()&&(tname()=='元素' || tname()=='链接')"><a
-                  v-on:mousedown="selectDescendents(1,1)">Select child elements (RFSE)</a><span
-                  title="Relative to First Selected Element, will only select the common child elements between the first selected block and the rest of the blocks">☺</span>
-              </div>
-              <div v-if="existDescendents()&&(tname()=='元素' || tname()=='链接')"><a
-                  v-on:mousedown="selectDescendents(1,2)">Select child elements (RASE)</a><span
-                  title="Relative to All Selected Elements, will select only the common child elements that exist in all blocks">☺</span>
-              </div>
-              <div><a v-on:mousedown="confirmCollectMultiAndDescendents">Collect Data</a><span title=""></span>
-              </div>
-              <div v-if="tname()!='选择框' && tname()!='文本框' && !selectedDescendents"><a
-                  v-on:mousedown="loopClickEveryElement">Loop-click every {{ tname() | toEng }}</a><span
-                  title="Usually used to click every link in a list to open detail page to collect data">☺</span>
+            <div v-if="option!=100">
+              ● Already selected {{ numOfList() }} similar elements, <span
+                v-if="numOfReady()>0">and we find other{{ numOfReady() }} similar elements (If unsatisfied with auto-detected similar elements, you can continue to manually select the rest of the elements that you think are similar), </span>you
+              can:
+              <div class="innercontent">
+                <div v-if="numOfReady()>0"><a v-on:mousedown="selectAll">Select All</a><span title=""></span></div>
+                <div v-if="existDescendents()&&(tname()=='元素' || tname()=='链接')"><a
+                    v-on:mousedown="selectDescendents">Select child elements (Greedy)</a><span
+                    title="Select All child elements for all blocks">☺</span></div>
+                <div v-if="existDescendents()&&(tname()=='元素' || tname()=='链接')"><a
+                    v-on:mousedown="selectDescendents(1,1)">Select child elements (RFSE)</a><span
+                    title="Relative to First Selected Element, will only select the common child elements between the first selected block and the rest of the blocks">☺</span>
+                </div>
+                <div v-if="existDescendents()&&(tname()=='元素' || tname()=='链接')"><a
+                    v-on:mousedown="selectDescendents(1,2)">Select child elements (RASE)</a><span
+                    title="Relative to All Selected Elements, will select only the common child elements that exist in all blocks">☺</span>
+                </div>
+                <div><a v-on:mousedown="confirmCollectMultiAndDescendents">Collect Data</a><span title=""></span>
+                </div>
+                <div v-if="tname()!='选择框' && tname()!='文本框' && !selectedDescendents"><a
+                    v-on:mousedown="loopClickEveryElement">Loop-click every {{ tname() | toEng }}</a><span
+                    title="Usually used to click every link in a list to open detail page to collect data">☺</span>
+                </div>
+                <div v-if="tname()!='选择框' && tname()!='文本框' && !selectedDescendents"><a
+                    v-on:mousedown="loopMouseMove">Loop-mouse-move to every {{ tname() | toEng }}</a><span
+                    title=""></span></div>
+                <div><a v-on:mousedown="revoke">Revoke selection</a><span title=""></span></div>
               </div>
-              <div v-if="tname()!='选择框' && tname()!='文本框' && !selectedDescendents"><a
-                  v-on:mousedown="loopMouseMove">Loop-mouse-move to every {{ tname() | toEng }}</a><span
-                  title=""></span></div>
-              <div><a v-on:mousedown="revoke">Revoke selection</a><span title=""></span></div>
             </div>
+
           </div>
 
-        </div>
+          <div v-if="valTable.length>0">
+            <div class="toolkitcontain">
+              <table cellspacing="0" class="toolkittb2">
+                <tbody>
+                <th v-for="(i, index) in list.opp">
+                  <div>{{ i["name"] }}</div>
+                  <span v-bind:index="index" v-on:mousedown="removeField" title="Remove this field">×</span></th>
+                <th style="width:40px">Delete</th>
+                </tbody>
+              </table>
+              <table cellspacing="0" class="toolkittb4">
+                <tbody>
+                <tr v-for="i in valTable[0].length">
+                  <td v-for="j in list.opp.length">{{ valTable[j - 1][i - 1] }}</td>
+                  <td style="font-size: 22px!important;width:40px;cursor:pointer" v-bind:index="i-1"
+                      v-on:mousedown="deleteSingleLine">×
+                  </td>
+                </tr>
+                </tbody>
+              </table>
+            </div>
+          </div>
 
-        <div v-if="valTable.length>0">
-          <div class="toolkitcontain">
-            <table cellspacing="0" class="toolkittb2">
-              <tbody>
-              <th v-for="(i, index) in list.opp">
-                <div>{{ i["name"] }}</div>
-                <span v-bind:index="index" v-on:mousedown="removeField" title="Remove this field">×</span></th>
-              <th style="width:40px">Delete</th>
-              </tbody>
-            </table>
-            <table cellspacing="0" class="toolkittb4">
-              <tbody>
-              <tr v-for="i in valTable[0].length">
-                <td v-for="j in list.opp.length">{{ valTable[j - 1][i - 1] }}</td>
-                <td style="font-size: 22px!important;width:40px;cursor:pointer" v-bind:index="i-1"
-                    v-on:mousedown="deleteSingleLine">×
-                </td>
-              </tr>
-              </tbody>
-            </table>
+          <div v-if="valTable.length==0&&tname()!='下一页元素'"></div>
+          <div v-if="list.nl.length>0"
+               style="bottom:12px;position:absolute;color:black!important;left:17px;font-size:13px">
+            <div style="margin-bottom:5px">
+              <button v-on:mousedown="cancel">Deselect</button>
+              <button v-if="!selectStatus" v-on:mousedown="enlarge">Expand Path</button>
+            </div>
+            <p style="margin-left:16px;margin-bottom:0px">{{ lastElementXPath() }}</p>
           </div>
         </div>
-
-        <div v-if="valTable.length==0&&tname()!='下一页元素'"></div>
-
-        <div v-if="list.nl.length>0"
-             style="bottom:12px;position:absolute;color:black!important;left:17px;font-size:13px">
-          <div style="margin-bottom:5px">
-            <button v-on:mousedown="cancel">Deselect</button>
-            <button v-if="!selectStatus" v-on:mousedown="enlarge">Expand Path</button>
+        <div v-else-if="page==1">
+          ● Please input text:
+          <input id="WTextBox" v-model="text" autofocus="autofocus" type="text"></input>
+          <button style="margin-left:0px!important;" v-on:click="getInput">Confirm</button>
+          <button style="margin-left:0px!important;" v-on:click="cancelInput">Cancel</button>
+          <div style="text-align: justify;margin-top: 15px;padding-right: 15px;margin-left: 4px">
+            Inputting &lt;enter&gt; or &lt;ENTER&gt; represents the simulation of pressing the Enter key after input
+            is complete, which is applicable in situations where data can only be obtained through pressing the Enter
+            key.
           </div>
-          <p style="margin-left:16px;margin-bottom:0px">{{ lastElementXPath() }}</p>
         </div>
-      </div>
-      <div v-if="page==1">
-        ● Please input text:
-        <input id="WTextBox" v-model="text" autofocus="autofocus" type="text"></input>
-        <button style="margin-left:0px!important;" v-on:click="getInput">Confirm</button>
-        <button style="margin-left:0px!important;" v-on:click="cancelInput">Cancel</button>
-        <div style="text-align: justify;margin-top: 15px;padding-right: 15px;margin-left: 4px">
-          Inputting &lt;enter&gt; or &lt;ENTER&gt; represents the simulation of pressing the Enter key after input
-          is complete, which is applicable in situations where data can only be obtained through pressing the Enter
-          key.
+        <div v-else-if="page==2">
+          <span style="font-size: 15px"> ● Change Mode </span>
+          <select v-model="optionMode" @change="handleSelectChange">
+            <option value=0>Change to next option</option>
+            <option value=1>Change option by index</option>
+            <option value=2>Change option by value</option>
+            <option value=3>Change option by text</option>
+          </select>
+          <span style="font-size: 15px" v-if="optionMode == 3"> ● Option Text</span>
+          <span style="font-size: 15px" v-if="optionMode == 1"> ● Option Index</span>
+          <span style="font-size: 15px" v-if="optionMode == 2"> ● Option Value</span>
+          <input id="selectValue" v-if="optionMode != 0" v-model="optionValue" autoFocus="autofocus"
+                 type="text"></input>
+          <div>
+            <button style="margin-left:0px!important;" v-on:click="sendChangeSelect">Confirm</button>
+            <button style="margin-left:0px!important;" v-on:click="cancelInput">Cancel</button>
+          </div>
         </div>
-      </div>
-      <div v-if="page==2">
-        <span style="font-size: 15px"> ● Change Mode </span>
-        <select v-model="optionMode" @change="handleSelectChange">
-          <option value=0>Change to next option</option>
-          <option value=1>Change option by index</option>
-          <option value=2>Change option by value</option>
-          <option value=3>Change option by text</option>
-        </select>
-        <span style="font-size: 15px" v-if="optionMode == 3"> ● Option Text</span>
-        <span style="font-size: 15px" v-if="optionMode == 1"> ● Option Index</span>
-        <span style="font-size: 15px" v-if="optionMode == 2"> ● Option Value</span>
-        <input id="selectValue" v-if="optionMode != 0" v-model="optionValue" autoFocus="autofocus"
-               type="text"></input>
-        <div>
-          <button style="margin-left:0px!important;" v-on:click="sendChangeSelect">Confirm</button>
-          <button style="margin-left:0px!important;" v-on:click="cancelInput">Cancel</button>
+        <div v-else-if="page==3">
+          <span style="font-size: 15px"> ● Please right-click on the page and select the next page button/link. To cancel the pagination setup, click the option below.</span>
+          <div style="font-size: 15px" v-if="list.nl.length==1">
+            ● A single element has been selected, you can click the option below to confirm the pagination setup.
+          </div>
+          <div style="font-size: 15px; color: #c82333;" v-else-if="list.nl.length>1">
+            ● The page-turning operation can only be set for one element. Please click the option below to cancel the
+            page-turning operation and reselect.
+          </div>
+          <div class="innercontent">
+            <div><a href="#" v-on:mousedown="confirmNextPage" v-if="list.nl.length==1" style="margin-bottom: 5px">Confirm
+              Pagination Setup</a>
+            </div>
+            <div><a href="#" v-on:mousedown="cancelNextPage">Cancel Pagination Setup</a></div>
+          </div>
         </div>
       </div>
     </div>
-
   </div>
 </template>
 
@@ -445,6 +486,9 @@ export default {
     optionMode: 0,
     optionValue: "",
     mode: 0, //记录删除字段模式
+    nextPage: 0, //是否设置翻页操作
+    lastAction: "collectData", //记录上一次的操作
+    global: global,
   },
   mounted() {
     this.$nextTick(() => {
@@ -515,18 +559,35 @@ export default {
       this.selectedDescendents = false;
       this.selectStatus = false;
       this.nowPath = "";
+      console.log("initialized")
     },
     confirmCollectSingle: function () { //单元素确认采集
       collectSingle();
       clearEl();
+      this.nextPage = 1;
+      this.lastAction = "collectData";
     },
     confirmCollectMulti: function () { //无规律多元素确认采集
       collectMultiNoPattern();
       clearEl();
+      this.nextPage = 1;
+      this.lastAction = "collectData";
     },
     confirmCollectMultiAndDescendents: function () { //有规律多元素确认采集
       collectMultiWithPattern();
       clearEl();
+      this.nextPage = 1;
+      this.lastAction = "collectDataWithLoop";
+    },
+    setNextPage: function () { //设置下一页元素
+      this.page = 3;
+    },
+    confirmNextPage: function () { //确认下一页元素
+      this.loopClickSingleElement("nextPageFromIndexPage");
+    },
+    cancelNextPage: function () { //取消下一页元素
+      this.cancel();
+      this.nextPage = 1; //保留下一页元素设置
     },
     deleteSingleLine: function (event) { //删除单行元素
       let at = new Date().getTime()
@@ -574,6 +635,8 @@ export default {
       await new Promise(resolve => setTimeout(resolve, 500)); //因为nodejs点击后又会把当前元素加入到列表中,所以这里需要等待一下再清空
       // global.nodeList[0]["node"].click(); //点击元素
       clearEl();
+      this.nextPage = 0;
+      this.lastAction = "clickElement";
     },
     changeSelect: function () {
       this.page = 2;
@@ -607,8 +670,9 @@ export default {
           alert("Switch failed, may fail when actually executed, please note.");
         }
       }
-
       clearEl();
+      this.nextPage = 0;
+      this.lastAction = "changeSelect";
     },
     handleSelectChange: function () {
       console.log(this.optionMode, this.optionValue);
@@ -625,19 +689,29 @@ export default {
     mouseMove: function () {
       sendMouseMove();
       clearEl();
+      this.nextPage = 0;
+      this.lastAction = "mouseMove";
     },
     loopMouseMove: function () {
       sendLoopMouseMove();
       clearEl();
+      this.nextPage = 0;
+      this.lastAction = "loopMouseMove";
     },
-    loopClickSingleElement: async function () { //循环点击单个元素
-      sendLoopClickSingle(this.tname()); //识别下一页,循环点击单个元素和点击多个元素
+    loopClickSingleElement: async function (type = "") { //循环点击单个元素
+      let name = this.tname();
+      if (type == "nextPageFromIndexPage") {
+        name = "nextPageFromIndexPage"
+      }
+      sendLoopClickSingle(name); //识别下一页,循环点击单个元素和点击多个元素
       // if (this.tname() != "下一页元素") { //下一页元素不进行点击操作
       global.nodeList[0]["node"].focus(); //获得元素焦点
       await new Promise(resolve => setTimeout(resolve, 500)); //因为nodejs点击后又会把当前元素加入到列表中,所以这里需要等待一下再清空
       // global.nodeList[0]["node"].click(); //点击元素
       // }
       clearEl();
+      this.nextPage = 0;
+      this.lastAction = "clickElementWithLoop";
     },
     loopClickEveryElement: async function () { //循环点击每个元素
       sendLoopClickEvery(); //识别下一页,循环点击单个元素和点击多个元素
@@ -645,6 +719,8 @@ export default {
       await new Promise(resolve => setTimeout(resolve, 500)); //因为nodejs点击后又会把当前元素加入到列表中,所以这里需要等待一下再清空
       // global.nodeList[0]["node"].click(); //点击元素
       clearEl();
+      this.nextPage = 0;
+      this.lastAction = "clickEveryElementWithLoop";
     },
     setInput: function (batch = false) { //输入文字
       this.batch = batch;
@@ -677,7 +753,8 @@ export default {
       this.selectStatus = true;
       clearReady();
     },
-    getCurrentTitle: function () { //获取当前页面的Title
+    getCurrentTitle: function () {
+      //获取当前页面的Title
       // 获取文档中所有元素
       // const elements = document.querySelectorAll('*');
       // global.nodeList.push(elements[0]); //将页面第一个元素放入列表中

+ 14 - 5
Extension/manifest_v3/src/content-scripts/trail.js

@@ -62,6 +62,8 @@ export function trial(evt) {
                         let realXPath = parent_xpath + xpath;
                         xpaths.push(realXPath);
                     }
+                } else {
+                    xpaths.push(xpath);
                 }
                 for (let j = 0; j < xpaths.length; j++) {
                     let xpath = xpaths[j];
@@ -119,11 +121,18 @@ export function trial(evt) {
                 } else if (loopType == 1) {
                     let elementList = document.evaluate(parameters.xpath, document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
                     let element = elementList.snapshotItem(0);
-                    if (element != null) {
-                        clearEl(true);
-                        addEl(null, element);
-                        if (elementList.snapshotLength > 1) {
-                            selectAllElements();
+                    // if (element != null) {
+                    //     clearEl(true);
+                    //     addEl(null, element);
+                    //     if (elementList.snapshotLength > 1) {
+                    //         selectAllElements();
+                    //     }
+                    // }
+                    clearEl(true);
+                    for (let i = 0; i < elementList.snapshotLength; i++) {
+                        let element = elementList.snapshotItem(i);
+                        if (element != null) {
+                            addEl(null, element);
                         }
                     }
                 } else if (loopType == 2) {

+ 12 - 0
Extension/manifest_v3/src/style/toolkit.css

@@ -59,6 +59,18 @@
     padding-left: 5px !important;
 }
 
+.nextPage a{
+    color: darkviolet!important;
+}
+
+.nextPage a:hover{
+    color: blue!important;
+}
+
+.nextPage span{
+    color: darkviolet!important;
+}
+
 .tooltips button {
     margin-top: 7px !important;
     font-size: 13px;

+ 7 - 6
Readme.md

@@ -49,17 +49,18 @@ More features please scroll to the bottom of this page to view.
 
 Refer to the [Releases Page](https://github.com/NaiboWang/EasySpider/releases) to download the latest version of EasySpider.
 
-<!-- ## 支持作者/Support Author
+## 支持作者/Support Author
 
 易采集EasySpider是一款完全免费无广告的开源软件,软件开发和维护全靠作者用爱发电,因此您可以选择支持作者让作者有更多的热情和精力维护此软件,或者您使用了此软件进行了盈利,欢迎您通过下面的方式支持作者:
 
-1. 支付宝账号:[email protected],也可以扫描下方二维码。
-2. 微信收款:扫描下方二维码。
-3. PayPal账号:naibowang,也可以扫描下方二维码。
+1. Github Sponsor:直接点击右侧**Sponsor**按钮赞助。
+2. 支付宝账号:[email protected],也可以扫描下方二维码。
+3. 微信收款:扫描下方二维码。
+4. PayPal账号:naibowang,也可以扫描下方二维码。
 
-Support author at paypal if you like this software, or use it to make profit: naibowang
+You can support the author by clicking the **Sponsor** button at right side or pay via paypal: naibowang.
 
-![QRCodes](media/QRCODES.png) -->
+![QRCodes](media/QRCODES.png)
 
 
 

部分文件因为文件数量过多而无法显示