diff --git a/README.md b/README.md index addea2a..af32556 100644 --- a/README.md +++ b/README.md @@ -49,12 +49,12 @@ - 可启用/停用调度,自动计算下次执行时间;到点自动开跑,跑完自动计算下一次 ### 4. 自动爬取模式 -给定一个起始网址,系统自动从页面里发现链接、按规则筛选后 BFS 爬取: +给定一个起始网址,系统**持续递归**爬取:爬取页面 → 自动发现符合规则的链接 → 继续爬取 → 继续发现……直到**无新链接可爬**时自动结束。 - **🧪 试爬取**:先用起始网址试跑一次,展示规则筛选后的链接清单(将爬取哪些、排除哪些及原因),确认规则符合预期后再正式开爬 +- **最大页数(安全上限)**:无深度限制,但达到页数上限自动停止,防止动态无限链接的站点失控 - **包含规则**:只爬包含指定子串(或正则)的链接 - **排除规则**:跳过匹配的链接(如 login、/tag/) - **仅同域名**:限制在起始网站内 -- **最大页数 / 最大深度**:控制爬取规模 - 其余参数(间隔、重试、图片、通知)同批量模式 ### 5. 资源操作信息(元数据) diff --git a/engine.py b/engine.py index 2876168..c3be35b 100644 --- a/engine.py +++ b/engine.py @@ -567,11 +567,11 @@ class CrawlJob: return included def _crawl_auto(self): + """自动爬取: 持续递归 爬取->发现链接->爬取... 直到无新链接可爬或达到安全上限""" run = self.run auto = self.task.get("auto", {}) seed = auto.get("seed_url", "") - max_pages = int(auto.get("max_pages", 50) or 50) - max_depth = int(auto.get("max_depth", 2) or 2) + max_pages = int(auto.get("max_pages", 200) or 200) # 安全上限, 防失控 run["progress"]["total"] = max_pages out_dir = self._resolve_out_dir() run["out_dir"] = out_dir @@ -591,6 +591,7 @@ class CrawlJob: if key in visited: continue if len(visited) >= max_pages: + self._log("info", f"达到最大页数上限 {max_pages}, 停止") break visited.add(key) idx += 1 @@ -602,7 +603,8 @@ class CrawlJob: run["results"].append(entry) self._bump_stats(entry) self._persist() - if entry["status"] == "OK" and depth < max_depth: + # 无深度限制: 只要页面爬取成功就继续发现链接, 递归直到队列为空 + if entry["status"] == "OK": for link in self._discover_links(page): lk = normalize_url(link) if lk not in visited and lk not in queued: @@ -613,4 +615,6 @@ class CrawlJob: finally: self._close_browser(p, browser, ctx, cookie_file) run["progress"]["total"] = len(visited) + if not self._stop.is_set() and len(visited) < max_pages: + self._log("info", f"无新链接可爬, 任务结束 (共 {len(visited)} 页)") self._persist() diff --git a/static/app.js b/static/app.js index aec3441..250de28 100644 --- a/static/app.js +++ b/static/app.js @@ -82,7 +82,7 @@ function taskStatusBadge(t) { const GROUPS = [ { key: "batch", label: "📄 批量爬取", desc: "一次性爬取指定网址列表" }, { key: "scheduled", label: "⏰ 定时爬取", desc: "按间隔或 cron 表达式定时执行" }, - { key: "auto", label: "🤖 自动爬取", desc: "从起始网址自动发现链接并爬取" }, + { key: "auto", label: "🤖 自动爬取", desc: "从起始网址自动发现链接,持续递归爬取到无新链接为止" }, ]; function renderTasks() { @@ -434,8 +434,7 @@ async function openEdit(tid) { f.elements["exclude"].value = (t.auto.exclude || []).join("\n"); f.elements["same_domain"].checked = t.auto.same_domain !== false; f.elements["use_regex"].checked = !!t.auto.use_regex; - f.elements["max_pages"].value = t.auto.max_pages ?? 50; - f.elements["max_depth"].value = t.auto.max_depth ?? 2; + f.elements["max_pages"].value = t.auto.max_pages ?? 200; } syncScheduleUI(); $("formHint").textContent = t.running @@ -481,8 +480,7 @@ async function submitForm(e) { exclude: splitLines(f.elements["exclude"].value), same_domain: f.elements["same_domain"].checked, use_regex: f.elements["use_regex"].checked, - max_pages: parseInt(f.elements["max_pages"].value) || 50, - max_depth: parseInt(f.elements["max_depth"].value) || 2, + max_pages: parseInt(f.elements["max_pages"].value) || 200, }; } if (mode === "scheduled") { diff --git a/static/index.html b/static/index.html index 4816059..e0f23a3 100644 --- a/static/index.html +++ b/static/index.html @@ -116,7 +116,7 @@