@clawhub-yuluoxci-24e3181b07
Legado (阅读) Android app book source development skill. For creating book sources for novel/manga websites, debugging existing book source rules, querying Leg...
---
name: legado-book-source-developer
description: Legado (阅读) Android app book source development skill. For creating book sources for novel/manga websites, debugging existing book source rules, querying Legado knowledge (CSS selectors, rule formats, POST requests), and analyzing website HTML structure. Covers searchUrl, ruleSearch, ruleToc, ruleContent, ruleBookInfo configuration. Use only with user-owned or authorized websites.
---
# Legado Book Source Developer
A toolkit for creating, debugging, and managing Legado book sources. Includes analysis tools, real book source examples, and a knowledge base built from Legado source code.
## Quick Start
```
1. Detect encoding → 2. Fetch real HTML → 3. Query knowledge base → 4. Analyze structure → 5. Create book source
```
Rules should be derived from actual HTML analysis rather than assumptions.
## Usage Scope
This skill is intended for developing book sources for websites the user owns or has authorization to access. The included tools (URL analysis, HTML fetching, source uploading) should only be used on target sites with the user's explicit consent. Do not use these tools for unauthorized scraping, bypassing access controls, or accessing content without permission.
## Tools
### Knowledge Query
| Tool | Purpose |
|------|---------|
| `search_knowledge(query)` | Search knowledge base |
| `get_css_selector_rules()` | CSS selector reference (paginated) |
| `get_real_book_source_examples(limit)` | Real book source analysis results |
| `get_book_source_templates(limit)` | Proven book source templates |
| `read_file_paginated(path, page)` | Read large files with pagination |
| `list_all_knowledge_files()` | List all knowledge files |
### HTML Analysis
| Tool | Purpose |
|------|---------|
| `smart_fetch_html(url, method, body, headers, charset)` | Fetch HTML with encoding support |
| `smart_web_analyzer(html)` | Full page structure analysis |
| `smart_bookinfo_analyzer(html)` | Book info page analysis |
| `smart_toc_analyzer(html)` | Table of contents analysis |
| `smart_content_analyzer(html)` | Content page analysis |
### Book Source Management
| Tool | Purpose |
|------|---------|
| `edit_book_source(complete_source="JSON")` | Create/edit book source |
| `validate_book_source.py` | Validate book source JSON (in `tools/`) |
### Analysis Scripts (in `tools/`)
| Script | Deps | Purpose |
|--------|------|---------|
| `analyze_url.py` | requests, bs4 | Website analysis (encoding + structure + search API) |
| `analyze_url.sh` | curl | Website analysis (no Python required) |
| `quick_analyze.py` | requests, bs4 | Quick analysis with auto HTML storage |
| `js_param_analyzer.py` | requests, bs4 | JS parameter/endpoint analysis |
| `validate_book_source.py` | — | Book source JSON validation (no deps) |
| `upload_book_source.py` | requests | Upload book source to public image host (default: tu.406np.xyz) for shareable direct links |
> No Python? See `references/no_python_workflow.md` for using host MCP tools (browser, HTTP, code execution).
## 3-Phase Workflow
### Phase 1: Information Collection
**Step 1: Query Knowledge Base**
```
search_knowledge("CSS选择器格式 提取类型 @text @html @href @src")
get_real_book_source_examples(limit=5)
get_book_source_templates(limit=3)
```
**Step 2: Detect Encoding (once, at start)**
```
detect_charset(url="http://example.com")
```
- UTF-8 → omit charset (default)
- GBK/GB2312 → add `"charset":"gbk"` to all requests
**Step 3: Fetch Real HTML**
```
smart_fetch_html(url="http://example.com/search", charset="gbk")
smart_fetch_html(url="http://example.com/search", method="POST",
body="keyword={{key}}&t=1", charset="gbk")
```
**Step 4: Analyze Structure**
```
smart_web_analyzer(html="...")
smart_bookinfo_analyzer(html="...")
smart_toc_analyzer(html="...")
smart_content_analyzer(html="...")
```
### Phase 2: Review
1. Write rules based on knowledge base + real HTML analysis
2. Validate CSS selectors, extraction types, regex format
3. Handle special cases (no cover, lazy loading, merged info)
When uncertain, ask the user rather than guessing.
### Phase 3: Create Book Source
1. Prepare complete JSON with all required fields
2. Call `edit_book_source(complete_source="完整JSON")`
3. Output as standard JSON array (no comments, no code blocks)
## Rule String Format
```
CSS选择器@提取类型##正则表达式##替换内容
```
**Extraction Types:**
- `@text` — text content (includes children)
- `@ownText` — element text only (excludes children)
- `@html` — HTML structure
- `@textNode` — text nodes
- `@href` — link URL
- `@src` — image source
- `@js` — JavaScript processing
**Numeric Indices:**
- `.0` = first, `.-1` = last (NOT `:first-child` / `:last-child`)
**Text Selection:**
- `text.关键词` (NOT `:contains()`)
## Common Patterns
**Standard list with cover:**
```json
{"bookList": ".book-list .item", "name": ".title@text", "bookUrl": "a@href", "coverUrl": "img@src"}
```
**No cover on search page:**
```json
{"coverUrl": ""}
```
**Lazy loading images:**
```json
{"coverUrl": "img@data-original||img@src"}
```
**nextContentUrl rule:** Chapter number changes → SET it. Page number only → LEAVE EMPTY.
## Known Constraints
**Unsupported fields (not in Legado source):**
- `prevContentUrl` does not exist
- `:contains()` pseudo-class is not supported (use `text.关键词`)
- `:first-child` / `:last-child` are not supported (use `.0` / `.-1`)
**Recommended practices:**
- Base rules on real HTML analysis rather than assumptions
- Query the knowledge base before writing rules
- Detect encoding once at the start
**Required fields:** See `references/legado_data_structures.md` for complete field specs from BookSource.kt, SearchRule.kt, TocRule.kt, ContentRule.kt, BookInfoRule.kt.
## References
| File | Content |
|------|---------|
| `references/legado_development_guide.md` | Workflow, HTML patterns, encoding, regex, troubleshooting |
| `references/legado_data_structures.md` | Source code analysis: data structures, rule engine, DB schema |
| `references/Legado书源开发完整指南.md` | Comprehensive development guide |
| `references/用户交互指南.md` | Common scenario interaction flows |
| `references/方法-JS扩展类.md` | JavaScript API documentation |
| `references/Legado书源编码处理指南.md` | Encoding handling guide |
| `references/knowledge_base/book_sources/` | Real book source analysis (MD) |
| `references/book_source_database/book_sources/` | Book source database (JSON) |
## Most Used Patterns (from real sources)
**CSS Selectors:** `img`(40x), `h1`(30x), `div`(13x), `content`(12x), `intro`(11x), `h3`(9x)
**Extraction Types:** `@href`(81x), `@text`(72x), `src`(60x), `@html`(33x), `@js`(25x)
FILE:references/Legado书源开发_长记忆系统.md
# Legado书源开发 - 长记忆系统
> **版本**: 1.0
> **更新时间**: 2025-01-06
> **知识来源**: 900+ 文件、2448+ 条知识、134个真实书源、168个选择器规则
> **重要提醒**: 以下所有内容仅作参考,所有选择器必须在真实HTML上验证!
---
## 📋 目录
1. [CSS选择器规则](#1-css选择器规则)
2. [提取类型详解](#2-提取类型详解)
3. [正则表达式格式](#3-正则表达式格式)
4. [书源JSON结构](#4-书源json结构)
5. [POST请求配置](#5-post请求配置)
6. [真实书源模式](#6-真实书源模式)
7. [134个真实书源分析结果](#7-134个真实书源分析结果)
8. [常见陷阱与错误](#8-常见陷阱与错误)
9. [错误处理策略](#9-错误处理策略)
10. [最佳实践](#10-最佳实践)
---
## 1. CSS选择器规则
### 1.1 基础格式
```
选择器@提取类型##正则表达式##替换内容
```
### 1.2 常用选择器类型
| 选择器类型 | 示例 | 说明 |
|------------|------|------|
| 标签选择器 | `div`, `p`, `a`, `img` | 选择指定标签 |
| 类选择器 | `.class-name` | 选择指定class |
| ID选择器 | `#id-name` | 选择指定id |
| 属性选择器 | `[attr=value]`, `[attr^=prefix]` | 根据属性选择 |
| 伪类选择器 | `:first-child`, `:last-child`, `:nth-child(n)` | 根据位置选择 |
### 1.3 组合选择器
```css
/* 后代选择:div下的所有p */
div p
/* 子元素选择:div的直接子p */
div > p
/* 相邻兄弟:div后的第一个p */
div + p
/* 通用兄弟:div后的所有p */
div ~ p
```
### 1.4 选择器优先级
```
#id > .class tag[attr=value]:nth-child(2)@text
```
优先级:ID选择器 > 类选择器 > 标签选择器 > 属性选择器 > 伪类选择器
### 1.5 备选选择器
```css
选择器1@提取类型||选择器2@提取类型
```
示例:
```css
img.lazy@data-original||img@src // 优先取data-original,失败则取src
```
---
## 2. 提取类型详解
### 2.1 基础提取类型
| 提取类型 | 说明 | 示例 |
|---------|------|------|
| `@text` | 提取纯文本内容(自动去除HTML标签) | `h1@text` |
| `@html` | 提取完整HTML结构 | `div.content@html` |
| `@href` | 提取链接地址(a标签的href属性) | `a@href` |
| `@src` | 提取图片地址(img标签的src属性) | `img@src` |
| `@ownText` | 提取元素自身文本(不包含子元素文本) | `div@ownText` |
| `@textNode` | 提取指定索引的文本节点 | `div@textNode(2)` |
### 2.2 高级提取类型
| 提取类型 | 说明 | 示例 |
|---------|------|------|
| `@js` | 执行JavaScript代码处理 | `@js:document.querySelector('h1').textContent` |
| `@json` | 解析JSON并提取内容 | `@json:$.data.book.name` |
| `@attr(属性名)` | 提取任意属性值 | `img@attr(data-original)` |
### 2.3 提取类型使用场景
#### 场景1:提取文本内容
```html
<div class="title"><h1>小说书名</h1></div>
```
```css
"title": ".title h1@text" // 提取"小说书名"
```
#### 场景2:提取HTML结构
```html
<div class="content"><p>段落1</p><p>段落2</p></div>
```
```css
"content": ".content@html" // 提取完整HTML结构
```
#### 场景3:提取链接地址
```html
<a href="/book/123">书籍详情</a>
```
```css
"bookUrl": "a@href" // 提取"/book/123"
```
#### 场景4:提取图片地址
```html
<img src="cover.jpg" alt="封面" />
```
```css
"coverUrl": "img@src" // 提取"cover.jpg"
```
#### 场景5:提取自身文本(不含子元素)
```html
<div class="author">
<span class="label">作者:</span>
张三
</div>
```
```css
"author": ".author@ownText" // 仅提取"张三"
```
---
## 3. 正则表达式格式
### 3.1 基础格式
```
选择器@提取类型##正则表达式##替换内容
```
### 3.2 常用场景
#### 场景1:清理前缀
```html
<p class="author">作者:张三</p>
```
```css
"author": ".author@text##^作者:##" // 移除"作者:"前缀,结果:"张三"
```
#### 场景2:清理后缀
```html
<p class="update">更新时间:2024-01-06</p>
```
```css
"update": ".update@text##更新时间:$##" // 移除"更新时间:"后缀,结果:"2024-01-06"
```
#### 场景3:提取中间内容
```html
<p class="info">科幻灵异 | 作者:张三</p>
```
```css
"author": ".info@text##.*作者:(.*?)##$1" // 提取"作者:"后的内容,结果:"张三"
```
#### 场景4:替换内容
```html
<p class="content">第一行\n第二行\n第三行</p>
```
```css
"content": ".content@text##\n##<br>" // 将换行符替换为<br>标签
```
#### 场景5:多规则组合
```css
p.author@text##^作者:####\s+$## // 先移除前缀,再移除末尾空格
```
### 3.3 正则修饰符
| 修饰符 | 说明 | 示例 |
|--------|------|------|
| `i` | 忽略大小写 | `(?i)pattern` |
| `m` | 多行模式 | `(?m)pattern` |
| `s` | 单行模式(`.`匹配换行符) | `(?s)<script.*?</script>` |
### 3.4 常用正则模式
| 模式 | 说明 | 示例 |
|------|------|------|
| `^文本` | 匹配开头 | `^作者:` |
| `文本$` | 匹配结尾 | `更新时间:$` |
| `.*?` | 非贪婪匹配任意字符 | `作者:(.*?)` |
| `\s+` | 匹配一个或多个空白 | `\s+` |
| `[^|]*` | 匹配非\|的字符 | `^[^|]*` |
| `(\d+)` | 匹配数字 | `(\d+)万字` |
---
## 4. 书源JSON结构
### 4.1 完整结构
```js
{
"bookSourceName": "书源名称", // 书源名称
"bookSourceUrl": "https://example.com", // 书源主站地址
"bookSourceGroup": "小说", // 书源分组
"bookSourceType": 0, // 书源类型(0=文本, 1=音频, 2=图片, 3=文件, 4=视频)
"bookSourceComment": "书源说明", // 书源注释
"loginUrl": "https://example.com/login", // 登录地址
"concurrentRate": "", // 并发率
"header": "", // 请求头
"searchUrl": "/search?q={{key}}", // 搜索URL(支持POST配置)
"exploreUrl": "", // 发现URL
"enabled": true, // 是否启用
"enabledExplore": true, // 是否启用发现
"weight": 0, // 智能排序权重
"customOrder": 0, // 手动排序编号
"lastUpdateTime": 1704537600000, // 最后更新时间
// 搜索页规则
"ruleSearch": {
"bookList": ".book-item", // 书籍列表选择器
"name": ".title@text", // 书名提取规则
"author": ".author@text##作者:##", // 作者提取规则(支持正则清理)
"kind": ".kind@text", // 分类提取规则
"wordCount": ".word-count@text", // 字数提取规则
"lastChapter": ".last-chapter a@text", // 最新章节提取规则
"intro": ".desc@text", // 简介提取规则
"coverUrl": "img@src", // 封面图片链接
"bookUrl": "a@href" // 书籍详情页链接
},
// 书籍详情页规则
"ruleBookInfo": {
"name": "#book-title@text", // 书名
"author": "#author a@text##作者:##", // 作者
"kind": "#kind@text", // 分类
"wordCount": "#word-count@text", // 字数
"lastChapter": "#last-chapter a@text", // 最新章节
"intro": "#intro@text", // 简介
"coverUrl": "#cover img@src", // 封面
"tocUrl": "" // 目录页URL(可选)
},
// 目录页规则
"ruleToc": {
"chapterList": "#chapter-list li", // 章节列表选择器
"chapterName": "a@text", // 章节名提取规则
"chapterUrl": "a@href", // 章节URL提取规则
"preUpdateJs": "", // 更新前JS(可选)
"updateJs": "", // 更新JS(可选)
"nextTocUrl": "" // 下一页目录URL(可选)
},
// 正文内容规则
"ruleContent": {
"content": "#content@html##<script.*?</script>", // 正文内容(支持正则清理)
"nextContentUrl": "#next-chapter@href", // 下一页正文URL(可选)
"webJs": "", // WebView JS(可选)
"sourceRegex": "", // 源码替换正则(可选)
"replaceRegex": "" // 内容替换正则(可选)
},
// 发现页规则(可选)
"ruleExplore": {
"exploreList": ".explore-item", // 发现列表选择器
"title": ".title@text", // 标题
"name": ".name@text", // 书名
"author": ".author@text", // 作者
"kind": ".kind@text", // 分类
"wordCount": ".word-count@text", // 字数
"lastChapter": ".last-chapter@text", // 最新章节
"intro": ".intro@text", // 简介
"coverUrl": "img@src", // 封面
"bookUrl": "a@href" // 书籍URL
}
}
```
### 4.2 书源类型说明
| 类型值 | 书源类型 | 说明 |
|--------|----------|------|
| 0 | 文本书源 | 标准小说网站,支持文本内容提取 |
| 1 | 音频书源 | 有声小说网站,支持音频链接提取 |
| 2 | 图片书源 | 漫画网站,需配置图片提取规则 |
| 3 | 文件书源 | 文件下载网站,需配置文件链接提取规则 |
| 4 | 视频书源 | 视频网站,需配置视频链接提取规则 |
---
## 5. POST请求配置
### 5.1 基础POST配置
```js
"searchUrl": "https://example.com/search,{\"method\":\"POST\",\"body\":\"key={{key}}&page={{page}}\"}"
```
### 5.2 带请求头的POST配置
```js
"searchUrl": "https://example.com/search,{\"method\":\"POST\",\"body\":\"key={{key}}\",\"headers\":{\"User-Agent\":\"Legado/3.0\",\"Accept-Language\":\"zh-CN,zh;q=0.9\"}}"
```
### 5.3 带编码的POST配置
```js
// GBK编码
"searchUrl": "https://example.com/search,{\"method\":\"POST\",\"body\":\"keyword={{key}}&searchtype=all\",\"charset\":\"GBK\"}"
// UTF-8编码(可省略)
"searchUrl": "https://example.com/search,{\"method\":\"POST\",\"body\":\"keyword={{key}}&searchtype=all\"}"
```
### 5.4 需要WebView渲染的POST请求
```js
"searchUrl": "https://example.com/search,{\"method\":\"POST\",\"body\":\"key={{key}}\",\"webView\":true}"
```
### 5.5 复杂POST配置(使用JavaScript)
```js
"searchUrl": "<js>(() => { var ua = 'Mozilla/5.0...'; var headers = {'User-Agent': ua}; var body = 'keyword=' + String(key) + '&page=' + String(page); var option = {'charset': 'gbk', 'method': 'POST', 'body': String(body), 'headers': headers}; return 'https://example.com/search,' + JSON.stringify(option); })()</js>"
```
### 5.6 POST请求关键要点
1. **body必须保证是JavaScript的String类型**
```js
// ✅ 正确
"body": "keyword={{key}}&page={{page}}"
// ❌ 错误
"body": keyword={{key}}&page={{page}}
```
2. **变量是计算得到的尽量都用String()强转**
```js
"body": "keyword=" + String(key) + "&page=" + String(page)
```
3. **charset为utf-8时可省略**
```js
// 可省略charset
"searchUrl": "/search,{\"method\":\"POST\",\"body\":\"keyword={{key}}\"}"
```
4. **无特殊情况不需要请求头和webView**
```js
// 最简配置
"searchUrl": "/search,{\"method\":\"POST\",\"body\":\"keyword={{key}}\"}"
```
---
## 6. 真实书源模式
### 6.1 模式1:标准小说站(最常见)
**特点**:有封面、完整信息、独立标签
**HTML结构**:
```html
<div class="book-list">
<div class="book-item">
<img src="cover.jpg" class="cover" />
<a href="/book/1" class="title">书名</a>
<p class="author">作者:张三</p>
<p class="kind">分类:玄幻</p>
</div>
</div>
```
**典型规则**:
```js
{
"ruleSearch": {
"bookList": ".book-list .book-item",
"name": ".title@text",
"author": ".author@text##^作者:##",
"kind": ".kind@text##^分类:##",
"bookUrl": "a@href",
"coverUrl": "img@src"
}
}
```
### 6.2 模式2:笔趣阁类站点
**特点**:无封面、信息合并、需要正则拆分
**HTML结构**:
```html
<div class="hot_sale">
<a href="/biquge_317279/">
<p class="title">末日成神:我的我的我的都是我的异能</p>
<p class="author">科幻灵异 | 作者:钱真人</p>
<p class="author">连载 | 更新:第69章 魔师</p>
</a>
</div>
```
**典型规则**:
```js
{
"ruleSearch": {
"bookList": ".hot_sale",
"name": ".title@text",
"author": ".author:first-child@text##.*作者:##",
"kind": ".author:first-child@text##^[^|]*##",
"lastChapter": ".author:last-child@text##.*更新:##",
"bookUrl": "a@href",
"coverUrl": ""
}
}
```
### 6.3 模式3:聚合源(API型)
**特点**:返回JSON数据,使用JSONPath提取
**典型规则**:
```js
{
"ruleSearch": {
"bookList": "$.data.records",
"name": "$.book_name",
"author": "$.author",
"coverUrl": "$.thumb_url",
"kind": "{{$.status}},{{$.score}}",
"bookUrl": "<js>...</js>"
}
}
```
### 6.4 模式4:漫画站点
**特点**:图片封面、漫画专属字段
**HTML结构**:
```html
<div class="comic-list">
<div class="comic-item">
<img src="cover.jpg" class="cover" />
<a href="/comic/1" class="title">漫画名</a>
<p class="author">作者</p>
</div>
</div>
```
**典型规则**:
```js
{
"ruleSearch": {
"bookList": ".comic-list .comic-item",
"name": ".title@text",
"author": ".author@text",
"coverUrl": "img@src",
"bookUrl": "a@href"
}
}
```
---
## 7. 134个真实书源分析结果
### 7.1 统计数据
| 指标 | 数值 |
|------|------|
| 总书源数 | 134个 |
| 涵盖类型 | 小说、漫画、影视、有声书、直播 |
| 主要分组 | 未分类(39)、小说(9)、晴天聚合(5)、大灰狼聚合(4)、R18(4) |
### 7.2 最常用CSS选择器(Top 10)
| 选择器 | 使用次数 | 主要用途 |
|--------|----------|----------|
| `img` | 40次 | 封面图片、章节图片 |
| `h1` | 30次 | 书名、大标题 |
| `div` | 13次 | 内容容器、通用区块 |
| `content` | 12次 | 正文内容容器 |
| `intro` | 11次 | 书籍简介、描述 |
| `h3` | 9次 | 章节标题、小标题 |
| `span` | 9次 | 行内文本、标签信息 |
| `a` | 多次 | 链接地址、章节跳转 |
| `p` | 多次 | 段落文本、作者信息 |
| `li` | 多次 | 列表项、章节列表 |
### 7.3 最常用提取类型(Top 5)
| 提取类型 | 使用次数 | 主要用途 |
|----------|----------|----------|
| `@href` | 81次 | 书籍URL、章节URL、跳转链接 |
| `@text` | 72次 | 文本内容提取(书名、作者、章节名等) |
| `@src` | 60次 | 图片地址提取(封面、插图等) |
| `@html` | 33次 | HTML结构提取(正文内容、复杂描述) |
| `@js` | 25次 | JavaScript处理(动态内容、加密解密) |
### 7.4 特殊功能使用统计
| 功能 | 使用次数 | 说明 |
|------|----------|------|
| 正则表达式 | 42次 | 用于清理内容、提取特定信息 |
| XPath | 24次 | 用于复杂DOM结构选择 |
| JavaScript处理 | 8次 | 用于复杂逻辑处理 |
| JSONPath | 6次 | 用于API型书源数据提取 |
### 7.5 常见书源模式统计
| 模式类型 | 数量 | 说明 |
|----------|------|------|
| 标准小说站 | 68个 | 有封面、完整信息、独立标签 |
| 笔趣阁类 | 45个 | 无封面、信息合并、需要正则拆分 |
| API聚合源 | 13个 | 返回JSON数据、使用JSONPath提取 |
| 漫画站点 | 8个 | 图片为主、漫画专属字段 |
---
## 8. 常见陷阱与错误
### 8.1 选择器陷阱
#### 陷阱1:使用过于宽泛的选择器
```css
// ❌ 错误:匹配所有p标签
"author": "p@text"
// ✅ 正确:仅匹配class为author的p标签
"author": "p.author@text"
```
#### 陷阱2:混淆选择器优先级
```css
// ❌ 错误:选择器不够具体
"content": "div@html"
// ✅ 正确:使用更具体的选择器
"content": "div.content@html"
```
### 8.2 提取类型错误
#### 陷阱1:混淆@text和@ownText
```html
<div class="intro">
<p>简介开头</p>
这是简介内容
</div>
```
```css
// 提取"简介开头这是简介内容"
"intro": ".intro@text"
// 仅提取"这是简介内容"
"intro": ".intro@ownText"
```
#### 陷阱2:混淆@text和@html
```css
// ❌ 错误:正文应该用@html
"content": "#content@text"
// ✅ 正确
"content": "#content@html"
```
### 8.3 正则表达式错误
#### 陷阱1:正则表达式语法错误
```css
// ❌ 错误:未使用##包裹
"author": ".author@text /作者:(.*)/"
// ✅ 正确
"author": ".author@text##作者:(.*?)##"
```
#### 陷阱2:贪婪匹配导致过度匹配
```css
// ❌ 错误:贪婪匹配可能获取过多内容
"author": ".author@text##作者:(.*)##"
// ✅ 正确:非贪婪匹配
"author": ".author@text##作者:(.*?)##"
```
### 8.4 POST请求配置错误
#### 陷阱1:未正确配置请求体
```js
// ❌ 错误:缺少引号和格式
"searchUrl": "/search,method=POST,body=keyword={{key}}"
// ✅ 正确:使用JSON格式
"searchUrl": "/search,{\"method\":\"POST\",\"body\":\"keyword={{key}}\"}"
```
#### 陷阱2:未配置编码
```js
// ❌ 错误:GBK编码网站未指定charset
"searchUrl": "/search,{\"method\":\"POST\",\"body\":\"keyword={{key}}\"}"
// ✅ 正确
"searchUrl": "/search,{\"method\":\"POST\",\"body\":\"keyword={{key}}\",\"charset\":\"GBK\"}"
```
### 8.5 相对URL处理错误
```js
// ❌ 错误:bookSourceUrl未配置
"bookSourceUrl": "",
"searchUrl": "/search?q={{key}}" // 跳转错误
// ✅ 正确
"bookSourceUrl": "https://www.example.com",
"searchUrl": "/search?q={{key}}" // 自动转为完整URL
```
---
## 9. 错误处理策略
### 9.1 选择器容错
#### 策略1:使用备选选择器
```css
// 优先使用data-original,备选src
"coverUrl": "img.lazy@data-original||img@src"
```
#### 策略2:使用||提供默认值
```css
// 搜索页无封面时设置为空
"coverUrl": "img@src||"
```
### 9.2 正则表达式容错
#### 策略1:使用非贪婪匹配
```css
// 避免贪婪匹配
"author": ".author@text##作者:(.*?)##"
```
#### 策略2:使用边界匹配
```css
// 使用^和$确保精确匹配
"author": ".author@text##^作者:.*$##"
```
### 9.3 空值处理
#### 策略1:设置默认值
```css
// 提供默认值
"intro": ".intro@text||暂无简介"
```
#### 策略2:留空处理
```css
// 搜索页无封面时设置为空
"coverUrl": ""
```
### 9.4 编码处理
#### 策略1:指定正确编码
```js
// GBK编码网站
"searchUrl": "/search,{\"method\":\"POST\",\"body\":\"keyword={{key}}\",\"charset\":\"GBK\"}"
```
#### 策略2:使用JavaScript处理
```js
"searchUrl": "<js>java.post('https://example.com/search', 'keyword=' + encodeURIComponent('{{key}}'))</js>"
```
---
## 10. 最佳实践
### 10.1 选择器最佳实践
1. **优先使用class选择器**
```css
// ✅ 推荐
".book-title@text"
// ⚠️ 不推荐(过于特定)
"#book-title@text"
```
2. **避免使用过于复杂的选择器**
```css
// ❌ 避免
"div.book-list div.book-item div.title h1 a@text"
// ✅ 推荐
".title@text"
```
3. **使用伪类处理多个同名元素**
```css
// 提取第一个author标签
".author:first-child@text"
// 提取最后一个author标签
".author:last-child@text"
// 提取第3个author标签
".author:nth-child(3)@text"
```
### 10.2 规则结构最佳实践
1. **按照官方规范组织JSON结构**
```js
{
"bookSourceName": "书源名称",
"bookSourceUrl": "https://example.com",
"bookSourceType": 0,
"searchUrl": "/search?q={{key}}",
"ruleSearch": {},
"ruleBookInfo": {},
"ruleToc": {},
"ruleContent": {}
}
```
2. **保持规则的一致性**
```css
// 统一使用Default语法
"name": ".title@text"
"author": ".author@text"
"coverUrl": "img@src"
```
3. **为规则添加注释(可选)**
```js
{
// 搜索页规则
"ruleSearch": {
"bookList": ".book-item",
"name": ".title@text"
}
}
```
### 10.3 测试最佳实践
1. **使用真实HTML验证选择器**
```python
validate_selector_on_real_web(url="https://example.com", selector=".title@text")
```
2. **测试不同场景**
- 测试无封面情况
- 测试信息合并情况
- 测试懒加载图片
- 测试分页内容
- 测试动态加载内容
3. **逐步验证**
```css
// 先验证列表选择器
"bookList": ".book-item"
// 再验证单个元素
"name": ".title@text"
// 最后验证正则清理
"author": ".author@text##^作者:##"
```
### 10.4 性能最佳实践
1. **避免使用过于宽泛的选择器**
```css
// ❌ 避免
"content": "div@html"
// ✅ 推荐
"content": "#content@html"
```
2. **对于大型页面,使用更具体的选择器**
```css
// ✅ 推荐
"content": "#article-content@html"
```
3. **合理使用正则表达式,避免过度使用**
```css
// ❌ 避免:过度使用正则
"content": "#content@html##<script.*?</script>##<style.*?</style>##<div.*?</div>##"
// ✅ 推荐:仅清理必要内容
"content": "#content@html##<script.*?</script>"
```
### 10.5 维护最佳实践
1. **为书源添加版本信息**
```js
{
"bookSourceName": "书源名称 v1.0",
"bookSourceComment": "创建于2025-01-06"
}
```
2. **定期检查书源可用性**
- 测试搜索功能
- 测试书籍详情
- 测试目录加载
- 测试正文阅读
3. **记录网站结构变化**
- 记录修改历史
- 标注失效规则
- 提供备选方案
---
## 📚 附录
### A. 官方文档参考
- [Legado官方GitHub](https://github.com/gedoor/legado)
- [Legado官方Wiki](https://github.com/gedoor/legado/wiki)
- [书源开发指南](https://github.com/gedoor/legado/wiki/BookSourceRules)
### B. 知识库文件清单
| 文件名 | 大小 | 说明 |
|--------|------|------|
| legado_knowledge_base.md | 60KB | 完整版知识库 |
| css选择器规则.txt | 79KB | CSS选择器详解 |
| 书源规则:从入门到入土.md | 39KB | 书源规则文档 |
| Legado知识库.txt | 13KB | 核心知识整合 |
| 真实书源知识库.md | 8.5KB | 134个书源分析 |
| 真实书源模板库.txt | 8.1KB | 真实书源模板 |
| 真实书源分析结果.json | 11KB | 结构化分析数据 |
### C. 知识统计
| 指标 | 数值 |
|------|------|
| 处理文件数 | 900+ |
| 学习条目数 | 2448+ |
| 书源数量 | 134个真实书源 + 804个参考书源 |
| 选择器规则 | 168个 |
| 知识类型 | CSS选择器、提取类型、正则表达式、POST请求、书源结构等 |
---
## ⚠️ 重要提醒
1. **所有知识库内容仅作参考**,不能直接照搬知识库中的选择器
2. **所有选择器必须在真实HTML上验证**,禁止编造选择器
3. **必须访问真实网页进行分析**,确保规则准确性
4. **禁止Mock数据**,必须基于真实HTML结构编写规则
5. **优先使用高频选择器**(img、h1、div、content等),兼容性更好
6. **合理选择提取类型**,避免类型混淆
7. **处理特殊情况**(无封面、懒加载、信息合并),确保规则健壮性
8. **POST请求必须按照知识库规范编写**,确保格式正确
---
*本文档基于900+文件、2448+条知识、134个真实书源分析生成,仅供参考和学习使用。*
FILE:references/Legado书源开发完整指南.md
# Legado 书源开发完整指南
> **版本**: 3.0
> **更新时间**: 2026-03-13
> **基于**: 40万行Legado源码分析 + 134个真实书源 + 完整知识库
---
## 📚 目录
1. [核心数据结构](#核心数据结构)
2. [规则语法详解](#规则语法详解)
3. [书源开发流程](#书源开发流程)
4. [常见陷阱与错误](#常见陷阱与错误)
5. [实战案例](#实战案例)
6. [调试技巧](#调试技巧)
7. [最佳实践](#最佳实践)
8. [书源配置开关详解](#书源配置开关详解)
9. [附录](#附录)
---
## 核心数据结构
### BookSource(书源主类)
```kotlin
data class BookSource(
// 核心标识(必填)
var bookSourceUrl: String = "", // 书源地址(主键,唯一)
var bookSourceName: String = "", // 书源名称
// 书源类型
var bookSourceType: Int = 0, // 0:文本, 1:音频, 2:图片, 3:文件, 4:视频
var bookSourceGroup: String? = null, // 书源分组
// 状态控制
var enabled: Boolean = true, // 是否启用
var enabledExplore: Boolean = true, // 是否启用发现
var customOrder: Int = 0, // 手动排序编号
// 性能指标
var lastUpdateTime: Long = 0, // 最后更新时间
var respondTime: Long = 180000L, // 响应时间(毫秒)
var weight: Int = 0, // 智能排序权重
// 请求配置
var header: String? = null, // 请求头(JSON格式)
var enabledCookieJar: Boolean? = true, // 自动保存Cookie
var concurrentRate: String? = null, // 并发率限制
// 规则配置(核心)
var searchUrl: String? = null, // 搜索URL模板
var ruleSearch: SearchRule? = null, // 搜索规则
var ruleBookInfo: BookInfoRule? = null,// 书籍信息规则
var ruleToc: TocRule? = null, // 目录规则
var ruleContent: ContentRule? = null, // 正文规则
var ruleExplore: ExploreRule? = null, // 发现规则
// 高级功能
var jsLib: String? = null, // JS库(可复用函数)
var loginUrl: String? = null, // 登录地址
var loginUi: String? = null, // 登录UI
var loginCheckJs: String? = null, // 登录检测JS
var coverDecodeJs: String? = null, // 封面解密JS
var bookSourceComment: String? = null, // 注释
var variableComment: String? = null, // 自定义变量说明
// 扩展功能
var eventListener: Boolean = false, // 是否监听事件
var customButton: Boolean = false // 自定义按钮
)
```
### SearchRule(搜索规则)
```kotlin
data class SearchRule(
// 列表规则(必填)
var bookList: String? = null, // 书籍列表选择器
// 字段提取规则
var name: String? = null, // 书名(必填)
var author: String? = null, // 作者
var kind: String? = null, // 分类
var wordCount: String? = null, // 字数
var lastChapter: String? = null, // 最新章节
var intro: String? = null, // 简介
var coverUrl: String? = null, // 封面URL
var bookUrl: String? = null, // 书籍URL(必填)
// 验证规则
var checkKeyWord: String? = null // 校验关键词
)
```
### BookInfoRule(书籍信息规则)
```kotlin
data class BookInfoRule(
// 初始化
var init: String? = null, // 初始化规则
// 字段提取规则
var name: String? = null, // 书名(必填)
var author: String? = null, // 作者
var intro: String? = null, // 简介
var kind: String? = null, // 分类
var lastChapter: String? = null, // 最新章节
var updateTime: String? = null, // 更新时间
var coverUrl: String? = null, // 封面URL
var tocUrl: String? = null, // 目录URL
var wordCount: String? = null, // 字数
// 扩展字段
var canReName: String? = null, // 可重命名
var downloadUrls: String? = null // 下载链接
)
```
### TocRule(目录规则)
```kotlin
data class TocRule(
// 列表规则(必填)
var chapterList: String? = null, // 章节列表选择器
// 字段提取规则(必填)
var chapterName: String? = null, // 章节名称
var chapterUrl: String? = null, // 章节URL
// 分页和高级功能
var nextTocUrl: String? = null, // 目录下一页
var preUpdateJs: String? = null, // 更新前执行JS
var formatJs: String? = null, // 格式化JS
// 章节标记
var isVolume: String? = null, // 是否卷
var isVip: String? = null, // 是否VIP
var isPay: String? = null, // 是否付费
var updateTime: String? = null // 更新时间
)
```
### ContentRule(正文规则)
```kotlin
data class ContentRule(
// 内容规则(必填)
var content: String? = null, // 正文内容
// 分页和导航
var nextContentUrl: String? = null, // 下一页URL(⚠️ 仅用于真正的下一章)
var subContent: String? = null, // 副文规则(拼接在正文后)
var title: String? = null, // 标题(有些网站只能在正文中获取)
// 高级功能
var webJs: String? = null, // WebView注入JS
var replaceRegex: String? = null, // 替换规则
var imageStyle: String? = null, // 图片样式(默认/FULL)
var imageDecode: String? = null, // 图片解密JS
var payAction: String? = null, // 购买操作
var callBackJs: String? = null // 事件回调
)
```
---
## 规则语法详解
### 基本格式
```
选择器@提取类型##正则表达式##替换内容
```
**分隔符说明**:
- `@` - 分隔选择器和提取类型
- `##` - 分隔正则表达式
- 第二个 `##` 可省略(如果替换内容为空)
### 选择器类型
#### 1. 基础选择器
```css
/* 元素选择器 */
div@text
p@text
h1@text
a@href
img@src
/* 类选择器 */
.book-name@text
.author@text
.intro@text
/* ID选择器 */
#content@text
#main@html
```
#### 2. 属性选择器
```css
/* 存在属性 */
[href]@href
[class*=book]@text
/* 属性值匹配 */
[data-id="123"]@text
[href^="https://"]@href /* 以https://开头 */
[href$=".html"]@href /* 以.html结尾 */
[href*="book"]@href /* 包含book */
```
#### 3. 组合选择器
```css
/* 后代选择器 */
.container .item@text
.book-list .book-name@text
/* 子元素选择器 */
ul > li@text
.book-info > h1@text
/* 相邻兄弟选择器 */
h1 + p@text
.title + .author@text
/* 通用兄弟选择器 */
.chapter ~ .notes@text
```
#### 4. ⚠️ 数字索引选择器(重要)
**正数索引**:
```css
.class.item.0@text # 第一个
.class.item.1@text # 第二个
.class.item.2@text # 第三个
```
**负数索引**:
```css
.class.item.-1@text # 倒数第一个
.class.item.-2@text # 倒数第二个
.class.item.-3@text # 倒数第三个
```
**排除索引**:
```css
.class.item.0!1@text # 排除第1个
.class.item.0!1!2@text # 排除第1和第2个
```
**区间索引**:
```css
.class.item.[0:5]@text # 第0到第5个
.class.item.[1:3]@text # 第1到第3个
.class.item.[0:-1]@text # 第0到倒数第1个
```
**步长索引**:
```css
.class.item.[0:10:2]@text # 第0到第10个,每2个取一个
.class.item.[0:10:3]@text # 第0到第10个,每3个取一个
```
#### 5. 文本选择器(text.文本)
⚠️ **替代 `:contains()` 伪类选择器**
```css
text.下一章@href # 提取包含"下一章"文本的元素的href
text.作者@text # 提取包含"作者"文本的元素的文本
text.下一页@href # 提取包含"下一页"文本的元素的href
```
### 提取类型详解
#### @text - 提取所有文本
- 提取元素及其所有子元素的文本内容
- 包含子元素的文本
- 适用场景:书名、作者、正文等纯文本提取
```css
.title@text # 提取标题及其子元素的文本
.content@text # 提取正文内容
```
#### @ownText - 只提取当前元素的文本
- 只提取当前元素的文本
- 不包含子元素
- 适用场景:需要排除子元素文本时
```css
.content@ownText # 只提取当前div的文本
.author@ownText # 只提取作者名(不包含子标签)
```
#### @html - 提取完整HTML结构
- 提取元素及其子元素的完整HTML
- 保留所有标签和属性
- 适用场景:保留格式、调试
```css
.content@html # 提取包括p、span等标签的完整HTML
```
#### @textNode - 提取文本节点
- 提取所有文本节点
- 分段返回多个结果
- 适用场景:逐个处理文本节点
```css
.content@textNode # 返回多个文本节点数组
```
#### @属性名 - 提取属性值
- 提取指定属性的值
- 常用属性:href、src、class、id、value、data-* 等
```css
a@href # 提取链接地址
img@src # 提取图片地址
img@data-original # 提取懒加载图片地址
```
### 正则表达式
#### 基本格式
**格式1:删除匹配的内容**
```
选择器@提取类型##正则表达式
```
示例:`.author@text##^作者:` 删除"作者:"前缀
**格式2:替换匹配的内容**
```
选择器@提取类型##正则表达式##替换内容
```
示例:`.price@text##\\$(\\d+)##¥$1` 将$替换为¥
**格式3:使用捕获组提取**
```
选择器@提取类型##正则表达式(捕获组)##$1
```
示例:`.author@text##.*作者:(.*)##$1` 提取"作者:"后面的内容
#### 常用正则表达式
**清理前缀**:
```
##^作者: # 删除开头的"作者:"
##^《 # 删除开头的"《"
##^[^|]*\\| # 删除第一个"|"及其前面的内容
```
**清理后缀**:
```
##(.*)$ # 删除括号及其内容
##》$ # 删除结尾的"》"
##\\s+$ # 删除结尾的空白
```
**提取特定内容**:
```
##.*作者:(.*)##$1 # 提取"作者:"后面的内容
##第(\\d+)章##$1 # 提取章节号
##(\\d{4})-(\\d{2})-(\\d{2})##$1年$2月$3日 # 格式化日期
```
**清理广告和提示**:
```
##<div id="ad">[\\s\\S]*?</div> # 删除广告div
##请收藏本站|本章完|继续阅读## # 删除常见提示文本
##免费小说就上.*## # 删除网站推广文本
```
**多个清理规则**:
使用 `|` 分隔多个规则:
```
选择器@提取类型##规则1|规则2|规则3##
```
示例:
```json
{
"content": "#chaptercontent@html##<div id=\"ad\">[\\s\\S]*?</div>|请收藏本站|本章完##"
}
```
### JS语法
#### 单行JS
```javascript
@js:let result = java.getString("$.title");
```
#### 多行JS
```javascript
<js>
let title = java.getString("$.title");
let author = java.getString("$.author");
result = title + " - " + author;
</js>
```
---
## 书源开发流程
### 阶段1:准备和分析
#### 1.1 检测编码
```bash
检测 https://www.example.com 的编码
```
**重要**:
- GBK网站必须配置 `"charset":"gbk"`
- UTF-8为默认编码,可省略
#### 1.2 分析搜索页
```bash
帮我分析 https://www.example.com 的搜索页结构
```
**获取信息**:
- 搜索URL格式
- 书籍列表容器选择器
- 书名、作者、封面等元素选择器
- 是否有懒加载图片
- 信息是否合并
#### 1.3 分析详情页
```bash
帮我分析 https://www.example.com 的详情页结构
```
**获取信息**:
- 书籍信息位置
- 目录链接
- 封面、作者、简介等
#### 1.4 分析目录页
```bash
帮我分析 https://www.example.com 的目录页结构
```
**获取信息**:
- 章节列表选择器
- 章节名称、URL
- 是否有分页
#### 1.5 分析正文页
```bash
帮我分析 https://www.example.com 的正文页结构
```
**获取信息**:
- 正文容器选择器
- 是否有"下一章"按钮
- 是否有广告需要清理
- 是否有分页
### 阶段2:配置规则
#### 2.1 配置searchUrl
```json
{
"searchUrl": "/search?q={{key}}&page={{page}}"
}
```
**POST请求GBK编码**:
```json
{
"searchUrl": "/search,{\"method\":\"POST\",\"body\":\"key={{key}}\",\"charset\":\"gbk\"}"
}
```
#### 2.2 配置ruleSearch
```json
{
"ruleSearch": {
"bookList": ".book-list .book-item",
"name": ".title@text",
"author": ".author@text",
"coverUrl": "img@src",
"bookUrl": "a@href"
}
}
```
#### 2.3 配置ruleBookInfo
```json
{
"ruleBookInfo": {
"name": "h1@text",
"author": ".author@text",
"intro": ".intro@text",
"coverUrl": ".cover@src",
"tocUrl": ".toc-link@href"
}
}
```
#### 2.4 配置ruleToc
```json
{
"ruleToc": {
"chapterList": "#chapter-list a",
"chapterName": "text",
"chapterUrl": "href"
}
}
```
#### 2.5 配置ruleContent
```json
{
"ruleContent": {
"content": "#content@html##<div id=\"ad\">[\\s\\S]*?</div>##"
}
}
```
### 阶段3:测试和调试
#### 3.1 测试搜索
1. 导入书源到Legado
2. 进行搜索测试
3. 查看搜索结果是否正确
#### 3.2 测试详情页
1. 点击书籍
2. 查看书名、作者、封面等信息
3. 验证规则是否正确提取
#### 3.3 测试目录
1. 进入目录
2. 查看章节列表
3. 验证章节顺序和链接
#### 3.4 测试正文
1. 进入章节阅读
2. 查看正文内容
3. 检查广告是否清理
4. 验证"下一章"功能
---
## 常见陷阱与错误
### 1. ❌ 字段错误
#### 错误:使用不存在的字段
```json
{
"ruleContent": {
"nextContentUrl": "text.下一章@href",
"prevContentUrl": "text.上一章@href" // ❌ 此字段不存在!
}
}
```
**原因**:Legado源码中没有 `prevContentUrl` 字段
#### 错误:误用伪类选择器
```css
.name:first-child@text // ❌ 不支持
.name:last-child@text // ❌ 不支持
a:contains(下一章)@href // ❌ 不支持
```
**正确**:
```css
.name.0@text // ✅ 第一个
.name.-1@text // ✅ 倒数第一个
text.下一章@href // ✅ 文本选择器
```
### 2. ❌ @text vs @ownText 混淆
```html
<div class="author">
作者:<span>张三</span>
</div>
```
**错误**:
```json
{
"author": ".author@text" // 提取"作者:张三"
}
```
**正确**:
```json
{
"author": ".author@ownText" // 只提取"作者:"
}
```
### 3. ❌ 编码问题
**GBK网站未配置charset**:
```json
{
"searchUrl": "/search.php?key={{key}}" // ❌ GBK网站会出现乱码
}
```
**正确**:
```json
{
"searchUrl": "/search.php,{\"method\":\"POST\",\"body\":\"key={{key}}\",\"charset\":\"gbk\"}"
}
```
### 4. ❌ nextContentUrl 误用
**场景1:分页场景**
```html
<!-- URL: /chapter/1.html?page=1 -->
<!-- 下一页: /chapter/1.html?page=2 -->
```
**错误**:
```json
{
"ruleContent": {
"nextContentUrl": "text.下一页@href" // ❌ 这是分页,不是下一章
}
}
```
**正确**:留空或不设置 `nextContentUrl`
**场景2:下一章场景**
```html
<!-- URL: /chapter/1.html -->
<!-- 下一章: /chapter/2.html -->
```
**正确**:
```json
{
"ruleContent": {
"nextContentUrl": "text.下一章@href" // ✅ 这是真正的下一章
}
}
```
### 5. ❌ 懒加载图片未处理
```html
<img class="lazy" data-original="http://example.com/cover.jpg" src="placeholder.jpg"/>
```
**错误**:
```json
{
"coverUrl": "img@src" // ❌ 提取到placeholder
}
```
**正确**:
```json
{
"coverUrl": "img@data-original||img@src" // ✅ 优先data-original
}
```
### 6. ❌ 正则表达式错误
**缺少分隔符**:
```json
{
"author": ".author@text##^作者" // ❌ 缺少结束符
}
```
**正确**:
```json
{
"author": ".author@text##^作者:##"
}
```
**未处理边界情况**:
```json
{
"name": "h1@text" // 提取"《斗破苍穹》"
}
```
**正确**:
```json
{
"name": "h1@text##^《|》$##" // 提取"斗破苍穹"
}
```
### 7. ❌ POST请求配置错误
**method配置错误**:
```json
{
"searchUrl": "/search,{\"body\":\"key={{key}}\"}" // ❌ 缺少method
}
```
**正确**:
```json
{
"searchUrl": "/search,{\"method\":\"POST\",\"body\":\"key={{key}}\"}"
}
```
**body格式错误**:
```json
{
"searchUrl": "/search,{\"method\":\"POST\",\"body\":key={{key}}}" // ❌ body应为JSON字符串
}
```
**正确**:
```json
{
"searchUrl": "/search,{\"method\":\"POST\",\"body\":\"key={{key}}\"}"
}
```
### 8. ❌ JSONPath使用错误
**未以$.开头**:
```json
{
"name": "data.title" // ❌ 可能解析失败
}
```
**正确**:
```json
{
"name": "$.data.title" // ✅ 标准JSONPath
}
```
---
## 实战案例
### 案例1:标准小说站
**网站特征**:
- 完整的书籍信息(封面、作者、简介)
- 标准HTML结构
- UTF-8编码
**HTML结构**:
```html
<div class="book-list">
<div class="book-item">
<img src="cover.jpg" class="cover"/>
<h3 class="title">斗破苍穹</h3>
<p class="author">作者:天蚕土豆</p>
<p class="intro">简介内容...</p>
<a href="/book/1.html">详情</a>
</div>
</div>
```
**书源配置**:
```json
{
"bookSourceUrl": "https://www.example.com",
"bookSourceName": "标准小说站",
"bookSourceType": 0,
"searchUrl": "/search?q={{key}}&page={{page}}",
"ruleSearch": {
"bookList": ".book-list .book-item",
"name": ".title@text",
"author": ".author@text",
"coverUrl": ".cover@src",
"intro": ".intro@text",
"bookUrl": "a@href"
},
"ruleBookInfo": {
"name": "h1@text",
"author": ".author@text",
"intro": ".intro@text",
"coverUrl": ".cover@src",
"tocUrl": ".toc-link@href"
},
"ruleToc": {
"chapterList": "#chapter-list a",
"chapterName": "text",
"chapterUrl": "href"
},
"ruleContent": {
"content": "#content@text"
}
}
```
### 案例2:笔趣阁类(无封面,信息合并)
**网站特征**:
- 无封面图片
- 信息合并在一起
- 需要正则拆分
**HTML结构**:
```html
<div class="result-list">
<div class="result-item">
<h3><a href="/book/1">《斗破苍穹》</a></h3>
<p class="info">科幻灵异 | 作者:天蚕土豆 | 更新:第100章</p>
</div>
</div>
```
**书源配置**:
```json
{
"bookSourceUrl": "https://www.bqge.com",
"bookSourceName": "笔趣阁",
"bookSourceType": 0,
"searchUrl": "/search.php?q={{key}}",
"ruleSearch": {
"bookList": ".result-list .result-item",
"name": "h3 a@text##^《|》$##",
"author": ".info@text##.*作者:|\\|.*##",
"kind": ".info@text##^.*\\| |\\|.*##",
"lastChapter": ".info@text##.*更新:##",
"bookUrl": "h3 a@href"
},
"ruleBookInfo": {
"name": "h1@text##^《|》$##",
"author": ".author@text##^作者:##",
"intro": ".intro@text"
},
"ruleToc": {
"chapterList": "#list dd a",
"chapterName": "text",
"chapterUrl": "href"
},
"ruleContent": {
"content": "#content@text##免费小说就上.*|本章完##"
}
}
```
### 案例3:POST请求GBK编码
**网站特征**:
- POST请求
- GBK编码
- 中文网站
**书源配置**:
```json
{
"bookSourceUrl": "https://www.69shuba.com",
"bookSourceName": "69书吧",
"bookSourceType": 0,
"searchUrl": "/modules/article/search.php,{\"method\":\"POST\",\"body\":\"searchkey={{key}}&searchtype=all\",\"charset\":\"gbk\"}",
"ruleSearch": {
"bookList": ".newbox li",
"name": "a.0@text",
"author": "span.-1@text##.*:##",
"coverUrl": "img@src",
"bookUrl": "a.0@href"
},
"ruleBookInfo": {
"name": ".booknav2 h1@text",
"author": ".booknav2 a.0@text",
"intro": ".navtxt p.-1@text",
"coverUrl": ".bookimg2 img@src"
},
"ruleToc": {
"chapterList": "#catalog li",
"chapterName": "a@text",
"chapterUrl": "a@href"
},
"ruleContent": {
"content": ".txtnav@html##<p>.*?</p>|<script[\\s\\S]*?</script>##"
}
}
```
### 案例4:聚合源(JSON API)
**网站特征**:
- 返回JSON数据
- 使用JSONPath提取
**JSON响应**:
```json
{
"code": 0,
"data": {
"records": [
{
"book_name": "斗破苍穹",
"author": "天蚕土豆",
"thumb_url": "http://example.com/cover.jpg",
"abstract": "简介内容",
"last_chapter": "第100章"
}
]
}
}
```
**书源配置**:
```json
{
"bookSourceUrl": "https://api.example.com",
"bookSourceName": "聚合源",
"bookSourceType": 0,
"searchUrl": "/api/search?keyword={{key}}&page={{page}}",
"ruleSearch": {
"bookList": "$.data.records[*]",
"name": "$.book_name",
"author": "$.author",
"coverUrl": "$.thumb_url",
"intro": "$.abstract",
"lastChapter": "$.last_chapter",
"bookUrl": "<js>...</js>"
},
"ruleBookInfo": {
"name": "$.data.book_name",
"author": "$.data.author",
"intro": "$.data.abstract",
"coverUrl": "$.data.thumb_url"
},
"ruleToc": {
"chapterList": "$.data.chapters[*]",
"chapterName": "$.title",
"chapterUrl": "$.url"
},
"ruleContent": {
"content": "$.data.content"
}
}
```
---
## 调试技巧
### 1. 使用浏览器开发者工具
**步骤**:
1. 打开浏览器
2. 按F12打开开发者工具
3. 切换到Elements标签
4. 查看HTML结构
5. 测试CSS选择器
**测试选择器**:
```javascript
document.querySelector('.book-list')
document.querySelectorAll('.book-item')
document.querySelector('.title').textContent
```
### 2. 逐步验证规则
**验证顺序**:
1. 验证 bookList
2. 验证各个字段提取
3. 验证正则表达式
4. 验证完整流程
**方法**:
- 每次只验证一个规则
- 确认正确后再验证下一个
- 逐步缩小问题范围
### 3. 查看Legado调试日志
**步骤**:
1. 导入书源到Legado
2. 打开书源调试
3. 执行搜索/阅读
4. 查看详细日志
**查看内容**:
- 请求URL
- 响应内容
- 规则解析结果
- 错误信息
### 4. 使用@html调试
**用途**:查看HTML结构,调试选择器
**示例**:
```json
{
"name": ".title@html" // 返回完整HTML,查看结构
}
```
### 5. 常见问题排查
**搜索无结果**:
- 检查 bookList 是否正确
- 检查 searchUrl 格式
- 检查编码配置
**内容缺失**:
- 检查选择器是否匹配
- 检查是否动态加载
- 检查是否需要webView
**乱码问题**:
- 检查编码配置
- 确认网站编码类型
- 使用charset参数
---
## 最佳实践
### 1. 开发前准备
- ✅ 检测网站编码
- ✅ 分析HTML结构
- ✅ 使用真实HTML验证选择器
- ✅ 准备测试数据
### 2. 规则编写
- ✅ 优先使用类选择器 `.class-name`
- ✅ 使用ID选择器 `#id-name` 更精准
- ✅ 避免过深嵌套(最多3-4层)
- ✅ 使用数字索引 `.0`、`.-1` 替代伪类
- ✅ 合理使用正则清理广告
- ✅ 提取类型要准确(@text、@html、@ownText)
### 3. 特殊情况处理
- ✅ 懒加载:`img@data-original||img@src`
- ✅ 信息合并:使用正则拆分
- ✅ 动态加载:使用webView
- ✅ GBK编码:添加 `charset:"gbk"`
- ✅ POST请求:正确配置method和body
### 4. 测试验证
- ✅ 逐步验证每个规则
- ✅ 使用真实数据测试
- ✅ 检查边界情况
- ✅ 测试特殊字符
- ✅ 验证分页场景
### 5. 性能优化
- ✅ 避免不必要的规则
- ✅ 使用高效选择器
- ✅ 合理使用正则
- ✅ 减少网络请求
### 6. 代码规范
```json
{
"bookSourceUrl": "https://www.example.com",
"bookSourceName": "示例书源",
"searchUrl": "/search?q={{key}}",
"ruleSearch": {
"bookList": ".book-list .book-item",
"name": ".title@text",
"author": ".author@text",
"bookUrl": "a@href"
}
}
```
---
## 书源配置开关详解
### 1. 启用/禁用功能
#### 1.1 `enabled` - 书源启用状态
**作用**:控制书源是否在阅读App中显示和使用
```json
{
"enabled": true // 启用书源(默认)
}
```
| 值 | 说明 | 使用场景 |
|----|------|----------|
| `true` | 启用书源 | 正常使用的书源 |
| `false` | 禁用书源 | 临时关闭、维护中的书源 |
**⚠️ 注意**:
- 禁用后,该书源不会参与任何搜索和阅读操作
- 仍可在书源管理界面查看和编辑
- 用户也可以在App中手动禁用书源
#### 1.2 `enabledExplore` - 发现页启用
**作用**:控制书源是否出现在发现页推荐中
```json
{
"enabledExplore": true // 启用发现(默认)
}
```
| 值 | 说明 | 使用场景 |
|----|------|----------|
| `true` | 显示在发现页 | 正常推广的书源 |
| `false` | 不显示在发现页 | 测试中、仅搜索使用的书源 |
**使用场景**:
- 只用于搜索的书源:`enabledExplore: false`
- 仅供内部使用的测试源:`enabledExplore: false`
- 正式发布的优质源:`enabledExplore: true`
---
### 2. Cookie 功能
#### 2.1 `enabledCookieJar` - Cookie 自动保存
**作用**:控制是否自动保存网站返回的Cookie,下次请求时自动携带
```json
{
"enabledCookieJar": true // 自动保存Cookie(默认)
}
```
| 值 | 说明 | 使用场景 |
|----|------|----------|
| `true` | 自动保存Cookie | 需要登录状态、会话保持 |
| `false` | 不保存Cookie | 静态网站、每次独立请求 |
**✅ 需要开启的场景**:
- 需要登录才能访问的网站
- 有阅读限制的VIP内容
- 需要保持会话状态
- 反爬虫需要Cookie验证
**❌ 不需要开启的场景**:
- 完全公开的静态网站
- 每次请求独立、无状态
- Cookie会过期导致频繁失效
**⚠️ 注意**:
- Cookie保存在本地App中,不会自动清除
- 某些网站Cookie有效期很短,需要频繁刷新
- 敏感网站注意Cookie隐私问题
---
### 3. 请求头配置
#### 3.1 `header` - 自定义请求头
**作用**:自定义HTTP请求头,模拟浏览器、添加认证等
```json
{
"header": "{\"User-Agent\":\"Mozilla/5.0...\",\"Referer\":\"https://example.com\"}"
}
```
**常用请求头**:
| 请求头 | 说明 | 示例 |
|--------|------|------|
| `User-Agent` | 浏览器标识 | `"Mozilla/5.0 (Windows NT 10.0; Win64; x64)..."` |
| `Referer` | 来源页面 | `"https://www.example.com"` |
| `Cookie` | Cookie字符串 | `"session_id=xxx; token=yyy"` |
| `Authorization` | 认证信息 | `"Bearer xxx"` |
| `X-Requested-With` | AJAX标识 | `"XMLHttpRequest"` |
**完整示例**:
```json
{
"header": "{\n \"User-Agent\": \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36\",\n \"Referer\": \"https://www.example.com\",\n \"Accept\": \"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8\",\n \"Accept-Language\": \"zh-CN,zh;q=0.9\"\n}"
}
```
**⚠️ 注意**:
- `header` 字段必须是JSON字符串格式
- 与 `enabledCookieJar` 配合使用:`enabledCookieJar` 控制Cookie自动保存,`header` 手动设置Cookie
- 如果网站检测到反爬虫,可能需要设置多个请求头
---
### 4. 登录功能
#### 4.1 `loginUrl` - 登录地址
**作用**:设置登录页面URL,用户可手动登录保存Cookie
```json
{
"loginUrl": "https://www.example.com/login"
}
```
**使用场景**:
- 需要登录才能访问的VIP内容
- 有会员限制的网站
- 需要账号才能使用的书源
#### 4.2 `loginUi` - 登录界面配置
**作用**:自定义登录表单界面
```json
{
"loginUi": "<form>\n <input name=\"username\" label=\"用户名\"/>\n <input name=\"password\" type=\"password\" label=\"密码\"/>\n</form>"
}
```
#### 4.3 `loginCheckJs` - 登录检测脚本
**作用**:使用JS检测登录状态
```json
{
"loginCheckJs": "result.match(/退出|用户中心/)!=null"
}
```
**使用场景**:
- 自动检测登录是否成功
- 动态判断是否需要重新登录
- 检测VIP状态
**⚠️ 注意**:
- `loginCheckJs` 应返回 `true`(已登录)或 `false`(未登录)
- 结果保存在 `result` 变量中
---
### 5. 高级功能开关
#### 5.1 `eventListener` - 事件监听
**作用**:是否启用WebView事件监听(用于复杂交互)
```json
{
"eventListener": false // 默认关闭
}
```
| 值 | 说明 | 使用场景 |
|----|------|----------|
| `true` | 启用事件监听 | 需要监听页面事件、点击操作 |
| `false` | 关闭事件监听 | 普通静态页面 |
**使用场景**:
- 需要等待页面加载完成
- 需要监听特定元素点击
- 需要与页面交互获取数据
#### 5.2 `customButton` - 自定义按钮
**作用**:是否启用自定义按钮功能
```json
{
"customButton": false // 默认关闭
}
```
**使用场景**:
- 需要在阅读界面添加自定义按钮
- 需要额外操作(如签到、投票等)
---
### 6. 编码配置
#### 6.1 `charset` - 字符集设置
**作用**:指定请求/响应的字符编码,解决中文乱码问题
**位置**:在 `searchUrl` 或具体请求的配置JSON中
```json
{
"searchUrl": "/search?q={{key}},{\"charset\":\"gbk\"}"
}
```
| 编码 | 说明 | 使用场景 |
|------|------|----------|
| `"gbk"` | GBK编码 | 老式中文网站、部分小说站 |
| `"utf-8"` | UTF-8编码 | 现代网站(默认) |
| `"big5"` | BIG5编码 | 繁体中文网站 |
**⚠️ 注意**:
- GBK网站必须设置 `charset:"gbk"`,否则会出现乱码
- URL中的中文参数需要特殊处理:`java.encodeURI(key, 'GBK')`
**完整示例**:
```json
{
"searchUrl": "/search.php?key={{java.encodeURI(key, 'GBK')}}&page={{page}},\"method\":\"POST\",\"body\":\"key={{java.encodeURI(key, 'GBK')}}\",\"charset\":\"gbk\"}"
}
```
---
### 7. 并发控制
#### 7.1 `concurrentRate` - 并发率限制
**作用**:限制并发请求数,防止触发网站反爬虫
```json
{
"concurrentRate": "3" // 最多3个并发请求
}
```
**使用场景**:
- 反爬虫严格的网站
- 服务器响应较慢的网站
- 需要控制请求频率
**⚠️ 注意**:
- 并发率过高可能导致IP被封禁
- 并发率过低会降低加载速度
- 建议根据网站实际情况调整(默认3-5)
---
### 8. 图片处理
#### 8.1 `imageStyle` - 图片样式
**作用**:控制图片的显示方式
```json
{
"ruleContent": {
"imageStyle": "FULL" // 图片全宽显示
}
}
```
| 值 | 说明 | 使用场景 |
|----|------|----------|
| `"FULL"` | 全宽显示 | 漫画、大图 |
| 未设置(默认) | 原始大小 | 普通插图 |
#### 8.2 `imageDecode` - 图片解密JS
**作用**:对加密图片进行解密处理
```json
{
"ruleContent": {
"imageDecode": "baseUrl+'?token='+result"
}
}
```
**使用场景**:
- 图片URL需要添加token
- 图片有防盗链处理
- 图片需要特殊请求参数
#### 8.3 `coverDecodeJs` - 封面解密JS
**作用**:对封面图片进行解密处理(与imageDecode类似)
```json
{
"coverDecodeJs": "result.replace(/^http:/, 'https:')"
}
```
---
### 9. 性能指标
#### 9.1 `respondTime` - 响应时间
**作用**:设置请求超时时间(毫秒)
```json
{
"respondTime": 180000 // 180秒超时(默认)
}
```
**使用场景**:
- 网站响应较慢时增加超时时间
- 快速网站可缩短超时时间
- 避免长时间等待
#### 9.2 `weight` - 智能排序权重
**作用**:控制书源在搜索结果中的排序优先级
```json
{
"weight": 10 // 权重越高,排序越靠前
}
```
**使用场景**:
- 优质源设置高权重(如10-20)
- 普通源默认权重(0)
- 测试源设置低权重(如-10)
---
### 10. 注释和文档
#### 10.1 `bookSourceComment` - 书源注释
**作用**:添加书源的说明、版本、作者等信息
```json
{
"bookSourceComment": "版本: v1.2\\n作者: xxx\\n更新: 2026-03-13\\n说明: 这是一个优质小说站"
}
```
**建议内容**:
- 版本号
- 作者/维护者
- 更新日期
- 功能说明
- 已知问题
#### 10.2 `variableComment` - 变量注释
**作用**:说明自定义变量的用途
```json
{
"variableComment": "{\\n \"token\": \"API令牌\\n\",\\n \"uid\": \"用户ID\\n\"}"
}
```
---
### 配置开关速查表
| 配置项 | 类型 | 默认值 | 作用 | 常见场景 |
|--------|------|--------|------|----------|
| `enabled` | Boolean | `true` | 启用书源 | 临时关闭书源 |
| `enabledExplore` | Boolean | `true` | 发现页显示 | 测试源不显示 |
| `enabledCookieJar` | Boolean | `true` | 自动保存Cookie | 需要登录、会话保持 |
| `header` | String | `null` | 自定义请求头 | 反爬虫、认证 |
| `loginUrl` | String | `null` | 登录地址 | VIP内容 |
| `loginCheckJs` | String | `null` | 登录检测 | 自动检测登录状态 |
| `eventListener` | Boolean | `false` | 事件监听 | 复杂交互页面 |
| `charset` | String | `"utf-8"` | 字符编码 | GBK网站 |
| `concurrentRate` | String | `null` | 并发率 | 反爬虫限制 |
| `respondTime` | Long | `180000` | 超时时间 | 响应慢的网站 |
| `weight` | Int | `0` | 排序权重 | 优质源优先 |
| `imageStyle` | String | 默认 | 图片样式 | 漫画全宽显示 |
| `imageDecode` | String | `null` | 图片解密 | 加密图片 |
| `bookSourceComment` | String | `null` | 书源注释 | 版本、作者信息 |
---
### 常见配置场景
#### 场景1:普通静态网站
```json
{
"enabled": true,
"enabledExplore": true,
"enabledCookieJar": false
}
```
#### 场景2:需要登录的VIP网站
```json
{
"enabled": true,
"enabledExplore": false,
"enabledCookieJar": true,
"loginUrl": "https://www.example.com/login",
"loginCheckJs": "result.match(/退出/)!=null",
"header": "{\"Cookie\":\"session_id=xxx\"}"
}
```
#### 场景3:GBK编码的老式小说站
```json
{
"searchUrl": "/search.php?key={{java.encodeURI(key, 'GBK')}}&page={{page}},{\"charset\":\"gbk\"}",
"enabledCookieJar": true
}
```
#### 场景4:反爬虫严格的网站
```json
{
"concurrentRate": "2",
"respondTime": 60000,
"header": "{\n \"User-Agent\": \"Mozilla/5.0...\",\n \"Referer\": \"https://www.example.com\"\n}",
"enabledCookieJar": true
}
```
#### 场景5:漫画书源
```json
{
"bookSourceType": 2,
"ruleContent": {
"imageStyle": "FULL",
"imageDecode": "baseUrl+'?token='+result"
}
}
```
---
## 附录
### 常用选择器速查
| 选择器 | 说明 | 示例 |
|--------|------|------|
| `.class` | 类选择器 | `.title@text` |
| `#id` | ID选择器 | `#content@text` |
| `element` | 元素选择器 | `div@text` |
| `[attr]` | 属性选择器 | `[href]@href` |
| `[attr=val]` | 属性值选择器 | `[class="book"]@text` |
| `parent child` | 后代选择器 | `.book-list .book-item` |
| `parent > child` | 子元素选择器 | `ul > li` |
| `.class.0` | 第一个 | `.item.0@text` |
| `.class.-1` | 倒数第一个 | `.item.-1@text` |
| `text.文本` | 文本选择器 | `text.下一章@href` |
### 提取类型速查
| 类型 | 说明 | 示例 |
|------|------|------|
| `@text` | 所有文本 | `.content@text` |
| `@ownText` | 元素自身文本 | `.content@ownText` |
| `@html` | 完整HTML | `.content@html` |
| `@href` | 链接地址 | `a@href` |
| `@src` | 图片地址 | `img@src` |
| `@textNode` | 文本节点 | `.content@textNode` |
### 常用正则速查
| 正则 | 说明 | 示例 |
|------|------|------|
| `^` | 行首 | `^作者:##` |
| `$` | 行尾 | `》$##` |
| `.*` | 任意字符 | `##.*##` |
| `.*?` | 非贪婪 | `##.*?##` |
| `(.*?)` | 捕获组 | `##(.*?)##$1` |
| `\\d` | 数字 | `##\\d##` |
| `\\s` | 空白 | `##\\s##` |
| `|` | 或 | `规则1|规则2` |
---
*本指南基于40万行Legado源码分析、134个真实书源和完整知识库整理而成*
FILE:references/Legado书源编码处理指南.md
# Legado 书源编码处理指南
## 概述
在开发 Legado 书源时,编码问题是一个常见且重要的技术细节。特别是许多中文老网站使用 GBK 编码,如果不正确处理,会导致内容乱码或请求失败。
## 常见编码类型
- **UTF-8**: 默认编码,可省略 charset 参数
- **GBK**: 常见于中文老网站
- **GB2312**: GBK 的子集,中文老网站常见
- **GB18030**: 更完整的中文编码标准
## 如何判断网站编码
### 1. 查看 HTTP 响应头
在浏览器开发者工具(F12)中:
- 切换到 **Network** 标签
- 找到搜索或内容请求
- 查看 **Response Headers** 中的 `Content-Type`
示例:
```
Content-Type: text/html; charset=GBK
```
### 2. 查看 HTML meta 标签
```html
<meta charset="GBK" />
<!-- 或 -->
<meta http-equiv="Content-Type" content="text/html; charset=GBK">
```
### 3. 乱码现象
- 如果不指定 `charset`,中文显示为乱码(如 "ÎÒ°®ãá"),说明网站使用非 UTF-8 编码
- 常见于小说、资讯类老网站
## 编码配置方法
### 方式一:简单配置(推荐)
在 `searchUrl`、`bookInfoUrl` 等请求字段中,使用 JSON 对象配置:
```json
{
"searchUrl": "/modules/article/search.php,{\"method\":\"POST\",\"body\":\"searchkey={{key}}&searchtype=all\",\"charset\":\"gbk\"}"
}
```
**要点**:
- 使用逗号分隔 URL 和 JSON 配置
- `charset` 字段指定编码格式
- 对于 GBK,大小写均可(`gbk` 或 `GBK`)
### 方式二:高级配置(JavaScript)
对于复杂场景,使用 JavaScript 动态构造:
```javascript
@js:
var ua = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36";
var headers = {"User-Agent": ua};
var body = "keyword=" + String(key) + "&page=" + String(page);
var option = {
"charset": "gbk",
"method": "POST",
"body": String(body),
"headers": headers
};
"https://www.example.com/search," + JSON.stringify(option)
```
**要点**:
- `body` 必须用 `String()` 强转类型
- 使用 `JSON.stringify()` 序列化 option 对象
- option 对象与 URL 之间用逗号分隔
## 编码转换工具
### java.utf8ToGbk
将 UTF-8 编码的字符串转换为 GBK 编码。
```javascript
// utf8编码转gbk编码,返回String
java.utf8ToGbk(str: String)
```
**使用场景**:
- 需要将 UTF-8 字符串转换为 GBK 编码后发送给网站
- 网站 POST 请求体需要 GBK 编码
**示例**:
```javascript
var utf8Str = "你好世界";
var gbkStr = java.utf8ToGbk(utf8Str);
```
### java.encodeURI
对字符串进行 URI 编码,可指定编码格式。
```javascript
// 使用 UTF-8 编码(默认)
java.encodeURI(str: String)
// 指定编码格式
java.encodeURI(str: String, enc: String)
```
**使用场景**:
- URL 参数需要编码时
- 网站要求特定编码格式的 URL 参数
**示例**:
```javascript
// GBK 编码
java.encodeURI('你好', 'GBK')
// UTF-8 编码
java.encodeURI('你好', 'UTF-8')
```
## 常见问题解决
### 1. 乱码问题
**现象**:中文显示为乱码
**原因**:未指定 `charset` 参数或编码设置错误
**解决方法**:添加 `"charset": "gbk"`
```json
{
"searchUrl": "/search.php,{\"charset\":\"gbk\",\"method\":\"POST\",\"body\":\"keyword={{key}}\"}"
}
```
### 2. POST 请求乱码
**现象**:POST 提交的中文参数在服务器端乱码
**原因**:body 编码未指定
**解决方法**:
- 方式一:在 option 中指定 `charset`
- 方式二:使用 `java.encodeURI` 编码参数
```javascript
// 方式一
var option = {
"charset": "gbk",
"method": "POST",
"body": String(body)
};
// 方式二
var encodedKeyword = java.encodeURI(key, 'GBK');
var body = "keyword=" + encodedKeyword + "&page=" + page;
```
### 3. GET 请求乱码
**现象**:GET 请求参数乱码
**原因**:URL 参数编码格式不正确
**解决方法**:使用 `java.encodeURI` 编码参数
```javascript
// GBK 编码
var encodedKey = java.encodeURI(key, 'GBK');
var url = "/search.php?keyword=" + encodedKey + "&page=" + page;
```
## 完整示例
### 示例 1:69书吧(Default 规则)
```json
{
"bookSourceName": "69书吧",
"bookSourceUrl": "https://www.69shuba.com",
"bookSourceType": 0,
"searchUrl": "/modules/article/search.php,{\"method\":\"POST\",\"body\":\"searchkey={{key}}&searchtype=all\",\"charset\":\"gbk\"}",
"ruleSearch": {
"bookList": "[email protected]",
"name": "tag.a.0@text",
"author": "tag.span.-1@text##.*:",
"bookUrl": "tag.a.0@href",
"coverUrl": "tag.img@src"
}
}
```
### 示例 2:复杂 POST(JavaScript)
```json
{
"bookSourceName": "示例网站",
"searchUrl": "@js:\nvar ua = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36';\nvar headers = {'User-Agent': ua};\nvar body = 'keyword=' + String(key) + '&page=' + String(page);\nvar option = {'charset': 'gbk', 'method': 'POST', 'body': String(body), 'headers': headers};\n'https://www.example.com/search,' + JSON.stringify(option)",
"ruleSearch": {
"bookList": "[email protected]",
"name": "tag.h3@text"
}
}
```
### 示例 3:GBK URL 编码
```javascript
@js:
var keyword = java.encodeURI(key, 'GBK');
"https://example.com/search?keyword=" + keyword + "&page=" + page
```
## 最佳实践
### 1. 编码检测流程
```
1. 访问网站,打开开发者工具(F12)
2. 切换到 Network 标签
3. 执行搜索或获取内容
4. 查看响应头的 Content-Type
5. 如果包含 charset=GBK,必须在请求中指定 "charset":"gbk"
```
### 2. 编写规则时的检查清单
- [ ] 检查 HTTP 响应头的 Content-Type
- [ ] 检查 HTML meta 标签的 charset
- [ ] 如果是 GBK,添加 `"charset":"gbk"`
- [ ] POST 请求的 body 用 `String()` 包裹
- [ ] URL 参数使用 `java.encodeURI` 编码
- [ ] 测试中文关键字是否正常
### 3. 调试方法
使用日志输出检查编码:
```javascript
@js:
var response = java.ajax(url);
java.log("原始响应: " + response);
var decoded = java.utf8ToGbk(response);
java.log("GBK解码后: " + decoded);
```
## 注意事项
1. **charset 参数位置**:
- 必须在 JSON 对象中,用逗号与 URL 分隔
- 错误示例:`/search.php?charset=gbk` ❌
- 正确示例:`/search.php,{"charset":"gbk"}` ✅
2. **大小写问题**:
- `"charset":"gbk"` 和 `"charset":"GBK"` 都可以
- 推荐使用小写 `"gbk"` 以保持一致性
3. **编码转换顺序**:
- 如果需要从 UTF-8 转到 GBK,使用 `java.utf8ToGbk`
- 如果需要 URL 编码,使用 `java.encodeURI`
- 两个方法的顺序很重要,根据实际情况选择
4. **字符集兼容性**:
- GBK 是 GB2312 的超集,兼容 GB2312
- 如果网站使用 GB2312,指定 `charset="gbk"` 也可以正常工作
## 常见编码速查表
| 编码 | charset 值 | 适用场景 |
|------|------------|----------|
| UTF-8 | 可省略 | 现代网站(推荐) |
| GBK | "gbk" | 中文老网站 |
| GB2312 | "gbk" | 中文老网站(GBK 子集) |
| GB18030 | "gbk" | 完整中文标准(兼容 GBK) |
## 总结
编码处理是 Legado 书源开发的基础技能:
- **优先判断**:通过响应头或 HTML meta 标签判断编码
- **正确配置**:使用 `"charset":"gbk"` 指定编码
- **工具辅助**:使用 `java.utf8ToGbk` 和 `java.encodeURI` 处理特殊情况
- **充分测试**:用中文关键字测试,确保无乱码
掌握这些技巧,可以轻松应对各种编码场景,让书源更加稳定可靠。
FILE:references/book_source_database/book_sources/6757_奇书网 https---m.qishu99.cc-_20260218_103403.json
[
{
"bookSourceComment": "这是小说下载源",
"bookSourceName": "奇书网",
"bookSourceType": 3,
"bookSourceUrl": "https://m.qishu99.cc/",
"customOrder": 2,
"enabled": true,
"enabledCookieJar": false,
"enabledExplore": true,
"exploreUrl": "[\n {\n \t \"title\":\"🔖书库🔖\",\n \"url\":\"/all/index_{{page}}.html\",\n \"style\":\n {\n \t \"layout_flexGrow\":1,\n \"layout_flexBasisPersent\":1\n }\n },\n {\n \t \"title\":\"男生小说\",\n \"url\":\"/nansheng/index_{{page}}.html\",\n \"style\":\n {\n \t \"layout_flexGrow\":1,\n \"layout_flexBasisPersent\":0.25\n }\n },\n {\n \t \"title\":\"女生言情\",\n \"url\":\"/yanqing/index_{{page}}.html\",\n \"style\":\n {\n \t \"layout_flexGrow\":1,\n \"layout_flexBasisPersent\":0.25\n }\n },\n {\n \t \"title\":\"耽美同人\",\n \"url\":\"/tongren/index_{{page}}.html\",\n \"style\":\n {\n \t \"layout_flexGrow\":1,\n \"layout_flexBasisPersent\":0.25\n }\n },\n {\n \t \"title\":\"都市小说\",\n \"url\":\"/dushi/index_{{page}}.html\",\n \"style\":\n {\n \t \"layout_flexGrow\":1,\n \"layout_flexBasisPersent\":0.25\n }\n },\n {\n \t \"title\":\"玄幻奇幻\",\n \"url\":\"/xuanhuan/index_{{page}}.html\",\n \"style\":\n {\n \t \"layout_flexGrow\":1,\n \"layout_flexBasisPersent\":0.25\n }\n },\n {\n \t \"title\":\"武侠修真\",\n \"url\":\"/xiuzhen/index_{{page}}.html\",\n \"style\":\n {\n \t \"layout_flexGrow\":1,\n \"layout_flexBasisPersent\":0.25\n }\n },\n {\n \t \"title\":\"网游竞技\",\n \"url\":\"/wangyou/index_{{page}}.html\",\n \"style\":\n {\n \t \"layout_flexGrow\":1,\n \"layout_flexBasisPersent\":0.25\n }\n },\n {\n \t \"title\":\"历史军事\",\n \"url\":\"/lishi/index_{{page}}.html\",\n \"style\":\n {\n \t \"layout_flexGrow\":1,\n \"layout_flexBasisPersent\":0.25\n }\n },\n {\n \t \"title\":\"科幻灵异\",\n \"url\":\"/kehuan/index_{{page}}.html\",\n \"style\":\n {\n \t \"layout_flexGrow\":1,\n \"layout_flexBasisPersent\":0.25\n }\n },\n {\n \t \"title\":\"其他小说\",\n \"url\":\"/qita/index_{{page}}.html\",\n \"style\":\n {\n \t \"layout_flexGrow\":1,\n \"layout_flexBasisPersent\":0.25\n }\n },\n {\n \t \"title\":\"🔖排行🔖\",\n \"url\":\"/hot/index_{{page}}.html\",\n \"style\":\n {\n \t \"layout_flexGrow\":1,\n \"layout_flexBasisPersent\":1\n }\n },\n {\n \t \"title\":\"🔖推荐🔖\",\n \"url\":\"/recommendall/index_{{page}}.html\",\n \"style\":\n {\n \t \"layout_flexGrow\":1,\n \"layout_flexBasisPersent\":1\n }\n },\n {\n \t \"title\":\"🔖最新🔖\",\n \"url\":\"/new/index_{{page}}.html\",\n \"style\":\n {\n \t \"layout_flexGrow\":1,\n \"layout_flexBasisPersent\":1\n }\n }\n]",
"lastUpdateTime": "1764908000121",
"respondTime": 180000,
"ruleBookInfo": {
"author": "[email protected]@text##作者:",
"coverUrl": ".bookcover@img@src",
"downloadUrls": ".bookbutton@a@href",
"intro": "[email protected]@text",
"kind": "[email protected]@text##分类:",
"lastChapter": "[email protected]@text##更新:",
"name": "[email protected]@text"
},
"ruleContent": [],
"ruleExplore": {
"author": ".author@text",
"bookList": ".imgtextlist@li",
"bookUrl": "a@href",
"coverUrl": "img@src",
"intro": ".intro@text",
"name": ".title@text"
},
"ruleSearch": {
"author": ".author.0@a@text",
"bookList": ".imgtextlist@li",
"bookUrl": "a@href",
"coverUrl": "Img@src",
"intro": ".intro@text",
"lastChapter": ".author.1@text##更新:",
"name": ".title@text"
},
"ruleToc": [],
"searchUrl": "/e/search/index.php,{\n\t\"method\":\"post\",\n\t\"body\":\"show=title,softsay,softwriter&keyboard={{key}}&tbname=download&tempid=1&Submit22=搜索\"\n\t}",
"weight": 0
}
]
FILE:references/book_source_database/book_sources/6886_📥 爱去小说 https---www.aiqu999.com_20260218_103401.json
[
{
"bookSourceComment": "来源:关耳\n可搜书名和作者\n不能用的话,打开网址看一下是不是换域名了,源URL换成新域名即可\n主站:https://www.aiqu999.com(似乎是一个会自动跳转可用链接的链接)\n备用网址:\n - www.aiqu654.com\n - www.aqxsw222.com\n - www.727txt.com\n - www.27txt.La\n - www.527txt.com\n网站给出的防迷路网址:www.272txt.com\n\n2026.01.19\n修复搜索失效(@歌行灯)",
"bookSourceGroup": "📥 下载",
"bookSourceName": "📥 爱去小说",
"bookSourceType": 3,
"bookSourceUrl": "https://www.aiqu999.com",
"customButton": false,
"customOrder": 36,
"enabled": true,
"enabledCookieJar": true,
"enabledExplore": false,
"eventListener": false,
"lastUpdateTime": "1768761330453",
"respondTime": 180000,
"ruleBookInfo": {
"author": "##作者:([^<]+)##$1###",
"downloadUrls": "<js>\nurl = \"http://www.aiqu127.com\"+java.getString(\"@tag.center.3@a@href\");\njava.log(url)\nhtml = java.ajax(url);\n\njava.getStringList(\"@text.下载地址@href\",html,true)\n</js>",
"intro": "##小说简介:([\\s\\S]+?)</div##$1###"
},
"ruleContent": [],
"ruleExplore": [],
"ruleSearch": {
"author": "a.search-card-author@a@text##作者:",
"bookList": "#searchmain > .search-card",
"bookUrl": ".search-card-link@a@href",
"checkKeyWord": "穿进赛博游戏后逆袭成神\nhttps://www.aqxsw666.com/txt-xx/nsxs/cycs/txt-240907.htm",
"intro": ".search-card-content@text##.+?文案(.+)##$1###",
"kind": "{{@@[email protected]@text}}",
"lastChapter": "[email protected]@text",
"name": ".search-card-title@a@text##《|》",
"wordCount": ".search-card-content@text##.+?【(.+)】##$1###"
},
"ruleToc": [],
"searchUrl": "/search.asp?word={{key}},{\"charset\":\"gb2312\"}",
"weight": 0
}
]
FILE:references/book_source_database/book_sources/6891_📥 奇书网 https---www.qishu99.cc_20260218_103400.json
[
{
"bookSourceComment": "3qishu.com,qishu99.com,同一个站\n原本的手机版有信息错误并且实际上不能下载,我换成了电脑版,重写了除搜索地址外的全部代码,现在可以正常用了。\n(但是这样一来原来的发现页就不能用了,懒得再写于是把发现页删了)\n2026.1.20 @歌行灯",
"bookSourceGroup": "📥 下载",
"bookSourceName": "📥 奇书网",
"bookSourceType": 3,
"bookSourceUrl": "https://www.qishu99.cc",
"customButton": false,
"customOrder": 32,
"enabled": true,
"enabledCookieJar": false,
"enabledExplore": true,
"eventListener": false,
"lastUpdateTime": "1768912368633",
"respondTime": 180000,
"ruleBookInfo": {
"coverUrl": ".nrlist > dl > .pic > .pics3@src",
"downloadUrls": "https://www.qishu99.cc{{@@text.进入小说下载地址@href}}\n<js>java.ajax(result)</js>\n.downlist[0,1]@li@strong@a@href",
"intro": ".softsay_title > .softsayxq > .cont@text##声明:.*"
},
"ruleContent": [],
"ruleExplore": [],
"ruleSearch": {
"author": "[email protected]@[email protected]@text",
"bookList": ".slist",
"bookUrl": ".info > h4 > a@href",
"checkKeyWord": "重生空间",
"coverUrl": ".pic > a > img@src",
"intro": "[email protected]@text##小说简介:|\\[.+TXT下载\\]",
"kind": "[email protected]@[email protected]@text&&[email protected]@text##发布时间:\\d{4}-\\d{2}-\\d{2} \\| 小说状态:(.+) \\| 小说格式:.+ \\| 小说大小:(.+)##$1,$2",
"lastChapter": "[email protected]@font@text",
"name": ".info > h4 > a@text##《(.+)》全本TXT电子书下载##$1###"
},
"ruleToc": [],
"searchUrl": "/e/search/index.php,{\n\t\"method\":\"post\",\n\t\"body\":\"show=title,softsay,softwriter&keyboard={{key}}&tbname=download&tempid=1&Submit22=搜索\"\n\t}",
"weight": 0
}
]
FILE:references/book_source_database/book_sources/6929_爱奇小说网 https---m.aqxsw.com-_20260218_103359.json
[
{
"bookSourceComment": "搞不定搜索,百度后直接复制详情页看吧",
"bookSourceName": "爱奇小说网",
"bookSourceType": 0,
"bookSourceUrl": "https://m.aqxsw.com/",
"customButton": false,
"customOrder": 7,
"enabled": true,
"enabledCookieJar": false,
"enabledExplore": true,
"eventListener": false,
"exploreUrl": "玄幻魔法::/fenlei/1/\n武侠修真::/fenlei/2/\n都市言情::/fenlei/3/\n历史军事::/fenlei/4/\n科幻恐怖::/fenlei/5/\n网游动漫::/fenlei/6/\n穿越言情::/fenlei/7/",
"lastUpdateTime": "1770440607865",
"respondTime": 182949,
"ruleBookInfo": {
"author": "[email protected]@text##作者:",
"coverUrl": "img@src",
"intro": "class.introbar@text##简介:",
"kind": "[email protected]@text##类别:",
"lastChapter": "class.newest@a@text",
"name": "[email protected]@text",
"tocUrl": "/index.html"
},
"ruleContent": {
"content": "id.content@html",
"nextContentUrl": "[email protected]@href",
"title": ".title@text"
},
"ruleExplore": {
"author": "[email protected]@text##作者:",
"bookList": "id.list@article",
"bookUrl": "article@a@href",
"coverUrl": "img@src",
"intro": "class.intro@text##简介:",
"name": "[email protected]@text"
},
"ruleSearch": [],
"ruleToc": {
"chapterList": "-class.chapter@li",
"chapterName": "a@text",
"chapterUrl": "a@href",
"nextTocUrl": "[email protected]@href"
},
"weight": 0
}
]
FILE:references/common.js
function refreshVcode(div){
var t = Date.now();
$("#"+div).attr("src", "/index/vcode?t="+t);
}
//提示信息框
function toast(msg, timeout) {
timeout = timeout || 3000;
if (!document.getElementsByClassName('toast-wrap').length) {
var div = document.createElement('div');
div.className = 'toast-wrap';
div.innerHTML = '<span class="toast-msg"></span>';
document.getElementsByTagName("body")[0].appendChild(div);
}
document.getElementsByClassName('toast-wrap')[0].getElementsByClassName('toast-msg')[0].innerHTML = msg;
var toastTag = document.getElementsByClassName('toast-wrap')[0];
toastTag.style.display = "block";
setTimeout(function () {
toastTag.style.display = 'none';
}, timeout);
}
function is_mobile() {
var isMobile = null != navigator.userAgent.toLowerCase().match(/(ipod|iphone|android|coolpad|mmp|smartphone|midp|wap|xoom|symbian|j2me|blackberry|wince)/i);
return isMobile;
}
function login_page() {
var html = '';
var isLogin = Object.keys(store.get('user_info') || {}).length ? true : false;
if (isLogin) {
var user_info = store.get('user_info');
html = '<div class="loginnav"><font color="red">Hi,'+ user_info.username + '</font>\
<a href="/user/bookcase.html">我的书架</a> | \
<a href="javascript:void(0);" onclick="book.logout()">退出登录</a></div>';
} else {
html = '<div class="login-toppel"><form name="headLoginForm" id="headLoginForm" method="post" action="/user/login.html">\
<div class="userbar">账号:<div class="inp"><input type="text" name="username" id="username" /></div></div>\
<div class="userbar">密码:<div class="inp"><input type="password" name="password" id="password" /></div></div>\
<div class="userbar"><a class="int" href="javascript:void(0)" onclick="book.userHeadLogin()">用户登录</a>\
<a href="/user/register.html" class="txt">用户注册</a></div></form></div>';
}
document.writeln(html);
}
function search_page() {
var html = '<div class="header_search">' +
'<form class="searchForm" action="/user/search.html">' +
'<input class="search" name="q" type="text" autocomplete="off" placeholder="可搜书名和作者,请您少字也别输错字。" onfocus="this.focus();">' +
'<input type="submit" class="searchBtn" value="搜索" />' +
'</form>' +
'</div>';
document.writeln(html);
}
function getQuery(variable) {
var query = window.location.search.substring(1);
var vars = query.split("&");
for (var i = 0; i < vars.length; i++) {
var pair = vars[i].split("=");
if (pair[0] == variable) {
return pair[1];
}
}
return (false);
}
function getSelectedCheckbox(div,name){
var checks = "";
var val = "";
$("#"+div+" input[name='"+name+"']").each(function(){
if($(this).prop("checked")){
val = $(this).val();
if("" == checks){
checks += val;
}else{
checks += ","+val;
}
}
});
return checks;
}
//设置首页
function sethome(obj, vrl) {
try {
obj.style.behavior = 'url(#default#homepage)';
obj.setHomePage(vrl);
} catch (e) {
if (window.netscape) {
try {
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
} catch (e) {
alert("此操作被浏览器拒绝!\n请在浏览器地址栏输入“about:config”并回车\n然后将 [signed.applets.codebase_principal_support]的值设置为'true',双击即可。");
}
var prefs = Components.classes['@mozilla.org/preferences-service;1'].getService(Components.interfaces.nsIPrefBranch);
prefs.setCharPref('browser.startup.homepage', vrl);
} else {
alert("您的浏览器不支持,请按照下面步骤操作:1.打开浏览器设置。2.点击设置网页。");
}
}
}
//加入收藏
function addFavorite() {
URL = '/';
title = '';
try {
window.external.addFavorite(URL, title);
} catch (e) {
try {
window.sidebar.addPanel(title, URL, "");
} catch (e) {
alert("加入收藏失败,请使用Ctrl+D进行添加");
}
}
}
function search_before() {
if (document.cookie != "") {
bgt=bgt.slice(0, bgt.length-1);
}
$("#loading").css('display', 'block');
}
function search_after() {
$("#loading").css('display', 'none');
}
function list1() {
}
function list2() {
}
function list3() {
}
function read1() {
}
function read2() {
}
function read2_1() {
}
function read2_2() {
}
function read3() {
}
function read4() {
}
function footer() {
}
function tongji() {
}
function read_manage() {
document.write('背景颜色 \n' +
' <select name="bcolor" id="bcolor" onchange="changeOpts(this, 1)">\n' +
' <option style="background-color:#E9FAFF" value="#E9FAFF">底色</option>\n' +
' <option style="background-color: #f6f6f6;" value="#f6f6f6">默认</option>\n' +
' <option style="background-color: #ffffff" value="#ffffff">白色</option>\n' +
' <option style="background-color: #e3e8f7" value="#e3e8f7">淡蓝</option>\n' +
' <option style="background-color: #daebfc" value="#daebfc">蓝色</option>\n' +
' <option style="background-color: #ebeaea" value="#ebeaea">淡灰</option>\n' +
' <option style="background-color: #e7e3e6" value="#e7e3e6">灰色</option>\n' +
' <option style="background-color: #dedcd8" value="#dedcd8">深灰</option>\n' +
'<option style="background-color: #d8d7d7" value="#d8d7d7">暗灰</option>\n' +
'<option style="background-color: #e6fae4" value="#e6fae4">绿色</option>\n' +
'<option style="background-color: #f9fbdd" value="#f9fbdd">明黄</option>\n' +
' </select>\n' +
' 文字颜色\n' +
' <select name=txtcolor id=txtcolor onchange="changeOpts(this, 2)">\n' +
'<option value="#000000">字色</option>\n' +
'<option value="#000000">黑色</option>\n' +
'<option value="#ff0000">红色</option>\n' +
'<option value="#006600">绿色</option>\n' +
'<option value="#0000ff">蓝色</option>\n' +
'<option value="#660000">棕色</option>\n' +
' </select>\n' +
' 字体大小\n' +
' <select name=fonttype id=fonttype onchange="changeOpts(this, 3)">\n' +
'<option value="20px">字号</option>\n' + '<option value="12px" >小号</option>\n' +
'<option value="16px" >较小</option>\n' + '<option value="20px" >中号</option>\n' +
'<option value="22px" >较大</option>\n' +
'<option value="24px" >大号</option>\n' +
' </select>\n' +
' 鼠标双击滚屏\n' +
' <select name="scrollspeed" id="scrollspeed" onchange="changeOpts(this, 4)">\n' +
'<option value="5">滚屏</option>\n' +
' <option value="1">1</option>\n' +
' <option value="2">2</option>\n' +
' <option value="3">3</option>\n' +
' <option value="4">4</option>\n' +
' <option value="5">5</option>\n' +
' <option value="6">6</option>\n' +
' <option value="7">7</option>\n' +
' <option value="8">8</option>\n' +
' <option value="9">9</option>\n' +
' <option value="10">10</option>\n' +
' </select>');
}
function Book() {
this.user_info = store.get('user_info') || {};
this.token = Object.keys(this.user_info).length ? this.user_info.token : '';
}
Book.prototype = {
addBook: function (book_id) {
this.checkLogin();
var url = '/api/addBook';
var params = {'token': this.token, 'bid': book_id};
$.post(url, params, function (json) {
if (json.code == 0) {
toast('恭喜您已成功加入到书架中!');
$("#saveBookBtn").html("<font color=red>已加入书架</font>");
} else {
toast(json.msg);
}
}, 'json');
},
getBook: function () {
this.checkLogin();
var url = '/api/findBook';
var params = {'token': this.token};
$.post(url, params, function (json) {
var bookList = json.data;
if (json.code == 0) {
var interText = doT.template($("#tpl-bookcase").text());
$("#book-list").html(interText(bookList));
} else if (json.code == 201) {
location.href = '/user/login.html';
}
}, 'json');
},
delBook: function (book_id) {
this.checkLogin();
var url = '/api/delBook';
var params = {'token': this.token, 'bid': book_id};
$.post(url, params, function (json) {
if (json.code == 0) {
$('#book' + book_id).remove();
toast('删除成功!');
} else {
toast(json.msg);
}
}, 'json');
},
getBookcase: function (site_book_id) {
if (this.token == '' || this.token == undefined) {
return ;
}
var url = '/api/getBookMark';
var params = {'token': this.token, 'bid': site_book_id};
$.get(url, params, function (json) {
if (json.code == 0) {
if (json.data.id > 0) {
$("#saveBookBtn").html("<font color=red>已加入书架</font>");
}
}
}, 'json');
},
getBookMark: function (site_book_id, site_chapter_id) {
if (this.token == '' || this.token == undefined) {
return ;
}
var url = '/api/getBookMark';
var params = {'token': this.token, 'bid': site_book_id};
$.get(url, params, function (json) {
if (json.code == 0) {
if (json.data.site_chapter_id == site_chapter_id) {
$("#saveMarkTopBtn").html("<font color=red>已加入书签</font>");
$("#saveMarkBottomBtn").html("<font color=red>已加入书签</font>");
}
}
}, 'json');
},
readLog: function (type) {
type = parseInt(type) || 0;
if (type == 1) {
bookList = store.get('book_readlog') || [];
if (bookList.length) {
if (bookList.length >= 20) {
bookList.pop();
}
for (var i in bookList) {
if (parseInt(bookList[i].book_id) == parseInt(info.book_id)) {
bookList.splice(i, 1);
break;
}
}
}
bookList.unshift(info);
store.set('book_readlog', bookList);
} else if (typeof (doT) == 'object') {
bookList = store.get('book_readlog') || [];
var interText = doT.template($("#tpl-readlog").text());
$("#book-list").html(interText(bookList));
}
},
removeLog: function(book_id) {
bookList = store.get('book_readlog') || [];
for (var i in bookList) {
if (parseInt(bookList[i].book_id) == book_id) {
bookList.splice(i, 1);
break;
}
}
store.set('book_readlog', bookList);
$("#log" + book_id).remove();
},
chapterSort: function () { //章节排序
var oUl = document.getElementById('chapter-list');
var oLi = oUl.getElementsByTagName('li');
for (var i = 0, arr = []; i < oLi.length; i++) {
arr[i] = oLi[i];
}
arr.reverse();
for (var i = 0; i < arr.length; i++) {
oUl.appendChild(arr[i]);
}
var text = document.getElementById('order').innerText;
document.getElementById('order').innerText = (text == '[倒序]') ? '[正序]' : '[倒序]';
},
checkLogin: function () { //验证登录
if (this.token == '' || this.token == undefined) {
var referer = window.location.href;
location.href = '/user/login.html?referer='+encodeURIComponent(referer);
}
},
userLogin: function () { //用户登录
var username = $("#loginForm #username").val();
var password = $("#loginForm #password").val();
//var expire = $("#loginForm #usecookie").find('option:checked').val();
if (username == '') {
toast('请输入用户名!');
return false;
}
if (password == '') {
toast('请输入密码!');
return false;
}
var referer = getQuery('referer');
var url = '/api/login';
var params = {'username': username, 'password': password};
$.post(url, params, function (json) {
if (json.code == 0) {
store.set('user_info', json.data);
if ("" != referer && false != referer) {
location.href = decodeURIComponent(referer);
}else{
location.href = '/';
}
} else {
toast(json.msg);
}
}, 'json');
return false;
},
userHeadLogin: function () {
var username = $("#headLoginForm #username").val();
var password = $("#headLoginForm #password").val();
//var expire = $("#loginForm #usecookie").find('option:checked').val();
if (username == '') {
toast('请输入用户名!');
return false;
}
if (password == '') {
toast('请输入密码!');
return false;
}
var url = '/api/login';
var params = {'username': username, 'password': password};
$.post(url, params, function (json) {
if (json.code == 0) {
store.set('user_info', json.data);
location.href = '/';
} else {
toast(json.msg);
}
}, 'json');
return false;
},
userRegister: function () {
var username = $("#regForm #username").val();
var password = $("#regForm #password").val();
var repassword = $("#regForm #repassword").val();
var email = $("#regForm #email").val();
var xarg = /^[a-z0-9][a-z0-9@\-._]{5,31}$/i;
if (username == '' || !xarg.test(username)) {
toast('用户名格式有误,6-32位字母、数字或_.-@组成!');
return false;
}
if (password == '' || !xarg.test(password)) {
toast('密码格式有误,6-32位字母、数字或_.-@组成!');
return false;
}
if (repassword == '') {
toast('请输入确认密码!');
return false;
}
if (password != repassword) {
toast('输入的两次密码不一致!');
return false;
}
var url = '/api/register';
var params = {'username': username, 'password': password, 'email': email};
$.post(url, params, function (json) {
if (json.code == 0) {
store.set('user_info', json.data);
location.href = '/';
} else {
toast(json.msg);
}
}, 'json');
return false;
},
editPassword: function () {
this.checkLogin();
var password = $("#password").val();
var repassword = $("#repassword").val();
var xarg = /^[a-z0-9][a-z0-9@\-._]{5,31}$/i;
if (password == '' || !xarg.test(password)) {
toast('密码格式有误,6-32位字母、数字或_.-@组成!');
return false;
}
if (repassword == '') {
toast('请输入确认密码!');
return false;
}
if (password != repassword) {
toast('输入的两次密码不一致!');
return false;
}
var url = '/api/editPassword';
var params = {'token': this.token, 'password': password};
$.post(url, params, function (json) {
if (json.code == 0) {
toast('密码修改成功');
} else {
toast(json.msg);
}
}, 'json');
},
logout: function () {
store.remove('user_info');
location.href = '/';
},
userInfo: function () {
this.checkLogin();
var url = '/api/getInfo';
var params = {'token': this.token};
$.get(url, params, function (json) {
if (json.code == 0) {
var interText = doT.template($("#tpl-userinfo").text());
$("#info-more").html(interText(json.data));
}
}, 'json');
},
editInfo: function () {
this.checkLogin();
var nickname = $('#nickname').val();
var email = $('#email').val();
var sex = $("input[name='sex']:checked").val();
var url = '/api/editInfo';
var params = {'token': this.token, 'nickname': nickname, 'email': email, 'sex': sex};
$.post(url, params, function (json) {
if (json.code == 0) {
alert('资料修改成功');
} else {
toast(json.msg);
}
}, 'json');
},
addMark: function (bid, cid, cname) {
this.checkLogin();
//cname = encodeURIComponent(cname || '');
var url = '/api/mark';
var params = {'token': this.token, 'bid': bid, 'cid': cid, 'cname': cname};
$.post(url, params, function (json) {
if (json.code == 0) {
toast('书签加入成功!');
$("#saveMarkTopBtn").html("<font color=red>已加入书签</font>");
$("#saveMarkBottomBtn").html("<font color=red>已加入书签</font>");
} else {
toast(json.msg);
}
}, 'json');
},
userVote: function (bid) {
this.checkLogin();
var url = '/api/vote';
var params = {'token': this.token, 'bid': bid};
$.post(url, params, function (json) {
if (json.code == 0) {
toast('投票成功!');
} else {
toast(json.msg);
}
}, 'json');
},
bookStats: function (bid) {
var url = '/api/access';
$.post(url, {'bid': bid}, function (json) {}, 'json');
},
search: function () {
if ("" == vw) {
return ;
}
if ("" == abw) {
return ;
}
if ("" == ru) {
return ;
}
if ("" == jrt) {
return ;
}
if ("" == van) {
return ;
}
if ("" == fw) {
return ;
}
if ("" == cwl) {
return ;
}
if ("" == gpr) {
return ;
}
if ("" == uyoo) {
return ;
}
if ("" == tz) {
return ;
}
if ("" == euu) {
return ;
}
if ("" == tsn) {
return ;
}
if ("" == eju) {
return ;
}
if ("" == um) {
return ;
}
if ("" == fp) {
return ;
}
if ("" == dvm) {
return ;
}
if ("" == jpk) {
return ;
}
if ("" == deblkx) {
return ;
}
if ("" == ht) {
return ;
}
if ("" == azy) {
return ;
}
if ("" == sna) {
return ;
}
if ("" == wqx) {
return ;
}
if ("" == fpp) {
return ;
}
if ("" == rup) {
return ;
}
if ("" == jwj) {
return ;
}
if ("" == bgt) {
return ;
}
if ("" == qp) {
return ;
}
if ("" == yf) {
return ;
}
if ("" == cw) {
return ;
}
if ("" == wq) {
return ;
}
var keyword = decodeURIComponent(getQuery('q'));
if ("" == keyword) {
bookList = new Array();
bookList['search'] = new Array();
bookList['keyword'] = keyword || '';
var interText = doT.template($("#tpl-search").text());
$("#book-list").html(interText(bookList));
return ;
}
search_before();
var url = '/api/search';
var params = {'q':keyword,'vw':vw,'abw':abw,'ru':ru,'jrt':jrt,'van':van,'fw':fw,'cwl':cwl,'gpr':gpr,'uyoo':uyoo,'tz':tz,'euu':euu,'tsn':tsn,'eju':eju,'um':um,'fp':fp,'dvm':dvm,'jpk':jpk,'deblkx':deblkx,'ht':ht,'azy':azy,'sna':sna,'wqx':wqx,'fpp':fpp,'rup':rup,'jwj':jwj,'bgt':bgt,'qp':qp,'yf':yf,'cw':cw,'wq':wq,'sign':sign};
$.post(url, params, function (json) {
search_after();
if (json.code == 0) {
bookList = json.data;
bookList['keyword'] = keyword || '';
var interText = doT.template($("#tpl-search").text());
$("#book-list").html(interText(bookList));
} else {
toast(json.msg);
}
}, 'json');
},
report: function () {
var url = '/api/report';
var referer = getQuery('referer');
var bid = $("#reportForm #bid").val();
var cid = $("#reportForm #cid").val();
var content = $("#reportForm #content").val();
var report_types = getSelectedCheckbox('reportForm', 'report_type');
if ("" == report_types) {
toast('请选择一项错误类型');
return false;
}
var params = {'token': this.token, 'bid': bid, 'cid': cid, 'type': report_types, 'content': content};
$.post(url, params, function (json) {
refreshVcode('img_vcode');
if (json.code == 0) {
toast('反馈成功!');
if ("" != referer && false != referer) {
location.href = decodeURIComponent(referer);
} else {
location.href = '/';
}
} else {
toast(json.msg);
}
}, 'json');
}
}
window.book = new Book();
FILE:references/html_storage/8289e449cf5e5d694e113e9b846a7c0e.html
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="application/xhtml+xml;charset=utf-8" />
<title>斗破苍穹免费阅读_斗破苍穹天蚕土豆最新章节列表TXT下载_歌书网手机阅读</title>
<meta name="keywords" content="斗破苍穹免费阅读,斗破苍穹最新章节TXT下载" />
<meta name="description" content="斗破苍穹最新章节由网友提供,《斗破苍穹》情节跌宕起伏、扣人心弦,是一本情节与文笔俱佳的玄幻魔法,歌书网免费阅读斗破苍穹最新清爽干净的文字章节在线阅读TXT下载。" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no" />
<meta name="format-detection" content="telephone=no" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<meta property="og:type" content="novel"/>
<meta property="og:title" content="斗破苍穹"/>
<meta property="og:description" content="斗破苍穹最新章节免费阅读,《斗破苍穹》情节跌宕起伏、扣人心弦,还可以TXT下载,歌书网免费阅读斗破苍穹最新清爽干净的文字章节在线阅读。"/>
<meta property="og:image" content="http://image.gashuw.com/168/168070/168070s.jpg"/>
<meta property="og:novel:category" content="玄幻魔法"/>
<meta property="og:novel:author" content="天蚕土豆"/>
<meta property="og:novel:book_name" content="斗破苍穹"/>
<meta property="og:novel:read_url" content="http://m.gashuw.com/biquge_168070/28564053.html"/>
<meta property="og:url" content="http://m.gashuw.com/biquge_168070/"/>
<meta property="og:novel:status" content="连载中"/>
<meta property="og:novel:update_time" content="2019-04-02 07:59:51"/>
<meta property="og:novel:latest_chapter_name" content="新书元尊已在起点上传,欢迎大家阅读。"/>
<meta property="og:novel:latest_chapter_url" content="http://m.gashuw.com/biquge_168070/43982889.html"/>
<link rel="stylesheet" href="/css/reset.css?v20181028" />
<link rel="stylesheet" href="/css/bookinfo.css?v20181028" />
<link rel="canonical" href="http://m.gashuw.com/biquge_168070/">
<script src="/js/wap.js"></script>
<script src="/js/gashuwcom.js"></script>
<script src="/4TeEtRQa/wnAzBmABVv.js"></script>
<script src="/4TeEtRQa/2XqnB2cHCo.js"></script>
<script src="/4TeEtRQa/FspjJ8bQJL.js"></script>
</head>
<body><div class="bg"></div>
<header class="channelHeader channelHeader2"><a href="javascript:history.go(-1);" class="iconback"><img src="/images/header-back.gif" alt="返回"/></a>
<span class="title">斗破苍穹</span><a href="/" class="iconhome"><img src="/images/header-backhome.gif" alt="首页"/></a>
</header><p class="synopsisAd" style="color: red;"></p>
<div class="synopsisArea">
<script>calfcaltp();</script>
<div class="synopsisArea_detail"><img src="http://image.gashuw.com/168/168070/168070s.jpg">
<p class="author">作者:天蚕土豆</p><p class="sort">类别:玄幻魔法</p><p class="">状态:连载中</p><p class="">点击:25046</p><p class="">更新:2019-04-02</p></div>
<p class="btn"><a href="/biquge_168070/28564053.html">立即阅读</a><a href="javascript:shujia(168070);" class="btn_toBookShelf">加入书架</a>
<a href="http://down.gashuw.com/txt/168070/斗破苍穹.txt" class="btn_toMyBook" rel="nofollow">TXT下载</a></p>
<script>calfcalmd();</script>
<p class="review"> 心潮澎湃,无限幻想,迎风挥击千层浪,少年不败热血!
</p>
</div>
<script>list1();</script><div id="center_tip"><b>最新网址:m.gashuw.com</b></div> <div class="recommend"><h2>最新章节 更新:2019-04-02</h2>
<div class="directoryArea"><p><a href='/biquge_168070/43982889.html'>新书元尊已在起点上传,欢迎大家阅读。</a></p><p><a href='/biquge_168070/28729840.html'>《斗破苍穹:斗帝之路》手游·角色传记(下)</a></p><p><a href='/biquge_168070/28729839.html'>《:斗帝之路》手游·角色传记(上)</a></p><p><a href='/biquge_168070/28729838.html'>萧炎云韵篇</a></p><p><a href='/biquge_168070/28729837.html'>萧玄魂天帝篇</a></p> <a name="all"></a>
<h2>全部章节 ( 斗破苍穹 )</h2>
<p><a href="/biquge_168070/28564053.html">上架感言</a></p> <p><a href="/biquge_168070/28564055.html">土豆三江访谈感言</a></p> <p><a href="/biquge_168070/28564058.html">封推感言</a></p> <p><a href="/biquge_168070/28564062.html">人物出场表!【未全!~】</a></p> <p><a href="/biquge_168070/28564067.html">推荐一本书:医狂天下</a></p> <p><a href="/biquge_168070/28564076.html">起点2009年年终作品盘点!</a></p> <p><a href="/biquge_168070/28564080.html">推荐一小MM新书:《小西游记》,</a></p> <p><a href="/biquge_168070/28564085.html">推荐朋友一本新书,内容不错:凡人修魂传</a></p> <p><a href="/biquge_168070/28564087.html">推荐一本美女的书:凤点江山</a></p> <p><a href="/biquge_168070/28564089.html">推荐一本不错的玄幻书:傲仙</a></p> <p><a href="/biquge_168070/28564091.html">推荐一本好书:大儒!</a></p> <p><a href="/biquge_168070/28564093.html">推荐一本新书 法神重生</a></p> <p><a href="/biquge_168070/28564098.html">推荐一本书:光甲旋风</a></p> <p><a href="/biquge_168070/28564101.html">四章完毕</a></p> <p><a href="/biquge_168070/28564109.html">萧炎,我们的主角的图片哦~</a></p> <p><a href="/biquge_168070/28564112.html">薰儿的图片</a></p> <p><a href="/biquge_168070/28564116.html">土豆强力推荐——《》网游</a></p> <p><a href="/biquge_168070/28564121.html">搜狐畅游获《》网游制作权 网游同步研发中</a></p> <p><a href="/biquge_168070/28564125.html">《》首次曝光的主人物形象</a></p> <p><a href="/biquge_168070/28564138.html">斗破网游,年内上市</a></p> <p><a href="/biquge_168070/28564143.html">第一章 陨落的天才</a></p> <p><a href="/biquge_168070/28564146.html">第二章 斗气大陆</a></p> <p><a href="/biquge_168070/28564159.html">第三章 客人</a></p> <p><a href="/biquge_168070/28564168.html">第四章 云岚宗</a></p> <p><a href="/biquge_168070/28564171.html">第五章 聚气散</a></p> <p><a href="/biquge_168070/28564173.html">第六章 炼药师</a></p> <p><a href="/biquge_168070/28564179.html">第七章 休!</a></p> <p><a href="/biquge_168070/28564183.html">第八章 神秘的老者</a></p> <p><a href="/biquge_168070/28564187.html">第九章 药老!</a></p> <p><a href="/biquge_168070/28564195.html">第十章 借钱</a></p> </div>
<div class="listpage">
<span class="left">
<a class="before" disabled="disabled">上一页</a></span>
<span class="middle">
<select name="pageselect" onchange="self.location.href=options[selectedIndex].value"><option value="/biquge_168070/1/#all" selected="selected">1 - 30章</option><option value="/biquge_168070/2/#all">31 - 60章</option><option value="/biquge_168070/3/#all">61 - 90章</option><option value="/biquge_168070/4/#all">91 - 120章</option><option value="/biquge_168070/5/#all">121 - 150章</option><option value="/biquge_168070/6/#all">151 - 180章</option><option value="/biquge_168070/7/#all">181 - 210章</option><option value="/biquge_168070/8/#all">211 - 240章</option><option value="/biquge_168070/9/#all">241 - 270章</option><option value="/biquge_168070/10/#all">271 - 300章</option><option value="/biquge_168070/11/#all">301 - 330章</option><option value="/biquge_168070/12/#all">331 - 360章</option><option value="/biquge_168070/13/#all">361 - 390章</option><option value="/biquge_168070/14/#all">391 - 420章</option><option value="/biquge_168070/15/#all">421 - 450章</option><option value="/biquge_168070/16/#all">451 - 480章</option><option value="/biquge_168070/17/#all">481 - 510章</option><option value="/biquge_168070/18/#all">511 - 540章</option><option value="/biquge_168070/19/#all">541 - 570章</option><option value="/biquge_168070/20/#all">571 - 600章</option><option value="/biquge_168070/21/#all">601 - 630章</option><option value="/biquge_168070/22/#all">631 - 660章</option><option value="/biquge_168070/23/#all">661 - 690章</option><option value="/biquge_168070/24/#all">691 - 720章</option><option value="/biquge_168070/25/#all">721 - 750章</option><option value="/biquge_168070/26/#all">751 - 780章</option><option value="/biquge_168070/27/#all">781 - 810章</option><option value="/biquge_168070/28/#all">811 - 840章</option><option value="/biquge_168070/29/#all">841 - 870章</option><option value="/biquge_168070/30/#all">871 - 900章</option><option value="/biquge_168070/31/#all">901 - 930章</option><option value="/biquge_168070/32/#all">931 - 960章</option><option value="/biquge_168070/33/#all">961 - 990章</option><option value="/biquge_168070/34/#all">991 - 1020章</option><option value="/biquge_168070/35/#all">1021 - 1050章</option><option value="/biquge_168070/36/#all">1051 - 1080章</option><option value="/biquge_168070/37/#all">1081 - 1110章</option><option value="/biquge_168070/38/#all">1111 - 1140章</option><option value="/biquge_168070/39/#all">1141 - 1170章</option><option value="/biquge_168070/40/#all">1171 - 1200章</option><option value="/biquge_168070/41/#all">1201 - 1230章</option><option value="/biquge_168070/42/#all">1231 - 1260章</option><option value="/biquge_168070/43/#all">1261 - 1290章</option><option value="/biquge_168070/44/#all">1291 - 1320章</option><option value="/biquge_168070/45/#all">1321 - 1350章</option><option value="/biquge_168070/46/#all">1351 - 1380章</option><option value="/biquge_168070/47/#all">1381 - 1410章</option><option value="/biquge_168070/48/#all">1411 - 1440章</option><option value="/biquge_168070/49/#all">1441 - 1470章</option><option value="/biquge_168070/50/#all">1471 - 1500章</option><option value="/biquge_168070/51/#all">1501 - 1530章</option><option value="/biquge_168070/52/#all">1531 - 1560章</option><option value="/biquge_168070/53/#all">1561 - 1590章</option><option value="/biquge_168070/54/#all">1591 - 1620章</option><option value="/biquge_168070/55/#all">1621 - 1650章</option><option value="/biquge_168070/56/#all">1651 - 1680章</option></select>
</span>
<span class="right"><a href="/biquge_168070/2/#all" class="onclick">下一页</a></span>
</div></div><br>
<div class="intui"><b>同类推荐</b><a href="/biquge_267398/">女侠且慢</a><a href="/biquge_264274/">国王</a><a href="/biquge_268784/">大燕斩妖人</a><a href="/biquge_219053/">剑道第一仙</a><a href="/biquge_268859/">凌天剑帝</a><a href="/biquge_254542/">大道神主</a><a href="/biquge_261435/">超凡:从恶魔开始</a><a href="/biquge_254558/">大玄印</a><a href="/biquge_238007/">战神王爷的小医妃</a><a href="/biquge_228389/">七世神盘</a><a href="/biquge_247539/">重生后,贵妃她人美路子野</a><a href="/biquge_256689/">侯门娇夫九千岁</a><a href="/biquge_269286/">太古神鼎诀</a><a href="/biquge_262985/">我有七个神兽奶娘</a><a href="/biquge_203920/">沧元图</a><a href="/biquge_261035/">我有一剑</a><a href="/biquge_207099/">傲世九重天</a><a href="/biquge_2181/">全职法师</a><a href="/biquge_81731/">诡秘之主</a><a href="/biquge_260990/">穿到远古部落种田搞基建</a></div>
<p class="Readpage" id="bottom"><script>mulu();</script></p><p class="note"></p>
<p class="note"></p>
<script>list2();</script>
<footer>
<a href="#top"><img src="/images/icon-backtop.gif" title="↑" alt="↑"/></a>
<p class="version channel">
<a href="/">首页</a>
<a href="/mybook.php" >我的书架</a>
<a href="/newcase.html" >阅读记录</a>
<a href="/map/sitemap.xml" target="_blank">网站地图</a>
<div style='display:none'>
<script>tj();recordedclick(168070);</script></div>
</footer>
<script src="//msite.baidu.com/sdk/c.js?appid=1613540281162979"></script>
<script type="application/ld+json">
{
"@context": "https://ziyuan.baidu.com/contexts/cambrian.jsonld",
"@id": "http://m.gashuw.com/biquge_168070/",
"appid": "1613540281162979",
"title": "斗破苍穹免费阅读_斗破苍穹天蚕土豆最新章节列表_歌书网手机阅读",
"images": ["http://image.gashuw.com/168/168070/168070s.jpg"],
"pubDate": "2019-04-02T07:59:51"
}
</script>
<script>cambrian.render('tail')</script>
</body></html>
FILE:references/html_storage/8289e449cf5e5d694e113e9b846a7c0e.meta.json
{
"url": "http://m.gashuw.com/biquge_168070/",
"size": 11041,
"timestamp": 1771407694.9349854,
"storage_path": "/workspace/projects/assets/html_storage/8289e449cf5e5d694e113e9b846a7c0e.html"
}
FILE:references/html_storage/bc4dacb45c3842ab2b1650c4cb33b4c8.html
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="application/xhtml+xml;charset=utf-8" />
<title>仙工开物免费阅读_仙工开物蛊真人最新章节列表TXT下载_歌书网手机阅读</title>
<meta name="keywords" content="仙工开物免费阅读,仙工开物最新章节TXT下载" />
<meta name="description" content="仙工开物最新章节由网友提供,《仙工开物》情节跌宕起伏、扣人心弦,是一本情节与文笔俱佳的修真小说,歌书网免费阅读仙工开物最新清爽干净的文字章节在线阅读TXT下载。" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no" />
<meta name="format-detection" content="telephone=no" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<meta property="og:type" content="novel"/>
<meta property="og:title" content="仙工开物"/>
<meta property="og:description" content="仙工开物最新章节免费阅读,《仙工开物》情节跌宕起伏、扣人心弦,还可以TXT下载,歌书网免费阅读仙工开物最新清爽干净的文字章节在线阅读。"/>
<meta property="og:image" content="http://image.gashuw.com/136/136435/136435s.jpg"/>
<meta property="og:novel:category" content="修真小说"/>
<meta property="og:novel:author" content="蛊真人"/>
<meta property="og:novel:book_name" content="仙工开物"/>
<meta property="og:novel:read_url" content="http://m.gashuw.com/biquge_136435/25558599.html"/>
<meta property="og:url" content="http://m.gashuw.com/biquge_136435/"/>
<meta property="og:novel:status" content="连载中"/>
<meta property="og:novel:update_time" content="2026-02-17 00:17:25"/>
<meta property="og:novel:latest_chapter_name" content="不碍此身向月驰!(广大书友2026春节快乐!)"/>
<meta property="og:novel:latest_chapter_url" content="http://m.gashuw.com/biquge_136435/81507220.html"/>
<link rel="stylesheet" href="/css/reset.css?v20181028" />
<link rel="stylesheet" href="/css/bookinfo.css?v20181028" />
<link rel="canonical" href="http://m.gashuw.com/biquge_136435/">
<script src="/js/wap.js"></script>
<script src="/js/gashuwcom.js"></script>
<script src="/4TeEtRQa/wnAzBmABVv.js"></script>
<script src="/4TeEtRQa/2XqnB2cHCo.js"></script>
<script src="/4TeEtRQa/FspjJ8bQJL.js"></script>
</head>
<body><div class="bg"></div>
<header class="channelHeader channelHeader2"><a href="javascript:history.go(-1);" class="iconback"><img src="/images/header-back.gif" alt="返回"/></a>
<span class="title">仙工开物</span><a href="/" class="iconhome"><img src="/images/header-backhome.gif" alt="首页"/></a>
</header><p class="synopsisAd" style="color: red;"></p>
<div class="synopsisArea">
<script>calfcaltp();</script>
<div class="synopsisArea_detail"><img src="http://image.gashuw.com/136/136435/136435s.jpg">
<p class="author">作者:蛊真人</p><p class="sort">类别:修真小说</p><p class="">状态:连载中</p><p class="">点击:939</p><p class="">更新:2026-02-17</p></div>
<p class="btn"><a href="/biquge_136435/25558599.html">立即阅读</a><a href="javascript:shujia(136435);" class="btn_toBookShelf">加入书架</a>
<a href="http://down.gashuw.com/txt/136435/仙工开物.txt" class="btn_toMyBook" rel="nofollow">TXT下载</a></p>
<script>calfcalmd();</script>
<p class="review"> 火山中,先贤大能遗留的机关仙宫,渴望着后继者。母亲舍命争取,获得仙宫宝印,临死前留给了宁拙。<br/><br/> 我佛心魔印!渡己为佛,渡人成魔。执掌此印,能刻下心印,指挥机关造物,负担极低。<br/><br/> 常人指挥机关,心神负担极重。但宁拙却能以一御万,游刃有余。宁拙:“娘,孩儿一定不负您的嘱托,取得那仙宫!”正是:仙偶通灵秘,工巧合至理。<br/><br/> 开宇出新境,物华与天齐。古钟传法度,月下舞清辉。真身具万象,人间谁与敌!<br/><br/> ……蛊真人下山之首作《仙工开物》将于2024.4.20,起点中文网首发。<br/><br/> 希望大家能喜欢!</p>
</div>
<script>list1();</script><div id="center_tip"><b>最新网址:m.gashuw.com</b></div> <div class="recommend"><h2>最新章节 更新:2026-02-17</h2>
<div class="directoryArea"><p><a href='/biquge_136435/81507220.html'>不碍此身向月驰!(广大书友2026春节快乐!)</a></p><p><a href='/biquge_136435/81507219.html'>第497章:问道于友</a></p><p><a href='/biquge_136435/81490006.html'>第496章:九曲回廊</a></p><p><a href='/biquge_136435/81490005.html'>第495章:铜牌</a></p><p><a href='/biquge_136435/81471515.html'>第494章:招揽</a></p> <a name="all"></a>
<h2>全部章节 ( 仙工开物 )</h2>
<p><a href="/biquge_136435/25558599.html">出山的三个挑战</a></p> <p><a href="/biquge_136435/25558601.html">抱歉!对今日上盟未果的书友们的补偿约定!</a></p> <p><a href="/biquge_136435/25558602.html">签约状态!敬请优先打赏角色卡</a></p> <p><a href="/biquge_136435/25558603.html">单日百盟!</a></p> <p><a href="/biquge_136435/25558605.html">番外1:宁拙早智(向书友们致歉!)</a></p> <p><a href="/biquge_136435/25558606.html">番外2:宁家放榜</a></p> <p><a href="/biquge_136435/25558607.html">第1章:垂髫客</a></p> <p><a href="/biquge_136435/25558608.html">第2章:宁拙</a></p> <p><a href="/biquge_136435/25558609.html">第3章:遗嘱</a></p> <p><a href="/biquge_136435/25558610.html">第4章:开物</a></p> <p><a href="/biquge_136435/25558612.html">第5章:此机关甚妙呀</a></p> <p><a href="/biquge_136435/25558614.html">第6章:我宁拙平平无奇</a></p> <p><a href="/biquge_136435/25558616.html">一天两百盟!</a></p> <p><a href="/biquge_136435/25558619.html">第7章:让宁拙回家来</a></p> <p><a href="/biquge_136435/25558622.html">第8章:我佛心魔印</a></p> <p><a href="/biquge_136435/25558627.html">两天三百盟!</a></p> <p><a href="/biquge_136435/25558630.html">第9章:普通人中计</a></p> <p><a href="/biquge_136435/25558633.html">第10章:火山地洞</a></p> <p><a href="/biquge_136435/25558635.html">第11章:到底是个少年</a></p> <p><a href="/biquge_136435/25558638.html">第12章:陷阱</a></p> <p><a href="/biquge_136435/25558642.html">第13章:果断动手</a></p> <p><a href="/biquge_136435/25558645.html">第14章:炸仙宫!</a></p> <p><a href="/biquge_136435/25558648.html">第15章:传法钟</a></p> <p><a href="/biquge_136435/25558651.html">第16章:地牢审问</a></p> <p><a href="/biquge_136435/25558654.html">第17章:渡己</a></p> <p><a href="/biquge_136435/25558655.html">第18章:费思献策</a></p> <p><a href="/biquge_136435/25558656.html">第19章:火爆猴和宁拙</a></p> <p><a href="/biquge_136435/25558658.html">月票番外致歉!</a></p> <p><a href="/biquge_136435/25558661.html">宁拙早智(向广大书友们致歉!)</a></p> <p><a href="/biquge_136435/25558662.html">第20章:龙鼋火灵</a></p> </div>
<div class="listpage">
<span class="left">
<a class="before" disabled="disabled">上一页</a></span>
<span class="middle">
<select name="pageselect" onchange="self.location.href=options[selectedIndex].value"><option value="/biquge_136435/1/#all" selected="selected">1 - 30章</option><option value="/biquge_136435/2/#all">31 - 60章</option><option value="/biquge_136435/3/#all">61 - 90章</option><option value="/biquge_136435/4/#all">91 - 120章</option><option value="/biquge_136435/5/#all">121 - 150章</option><option value="/biquge_136435/6/#all">151 - 180章</option><option value="/biquge_136435/7/#all">181 - 210章</option><option value="/biquge_136435/8/#all">211 - 240章</option><option value="/biquge_136435/9/#all">241 - 270章</option><option value="/biquge_136435/10/#all">271 - 300章</option><option value="/biquge_136435/11/#all">301 - 330章</option><option value="/biquge_136435/12/#all">331 - 360章</option><option value="/biquge_136435/13/#all">361 - 390章</option><option value="/biquge_136435/14/#all">391 - 420章</option><option value="/biquge_136435/15/#all">421 - 450章</option><option value="/biquge_136435/16/#all">451 - 480章</option><option value="/biquge_136435/17/#all">481 - 510章</option><option value="/biquge_136435/18/#all">511 - 540章</option><option value="/biquge_136435/19/#all">541 - 570章</option><option value="/biquge_136435/20/#all">571 - 600章</option><option value="/biquge_136435/21/#all">601 - 630章</option><option value="/biquge_136435/22/#all">631 - 660章</option><option value="/biquge_136435/23/#all">661 - 690章</option><option value="/biquge_136435/24/#all">691 - 720章</option><option value="/biquge_136435/25/#all">721 - 750章</option><option value="/biquge_136435/26/#all">751 - 780章</option><option value="/biquge_136435/27/#all">781 - 810章</option><option value="/biquge_136435/28/#all">811 - 840章</option><option value="/biquge_136435/29/#all">841 - 870章</option><option value="/biquge_136435/30/#all">871 - 900章</option><option value="/biquge_136435/31/#all">901 - 930章</option><option value="/biquge_136435/32/#all">931 - 960章</option></select>
</span>
<span class="right"><a href="/biquge_136435/2/#all" class="onclick">下一页</a></span>
</div></div><br>
<div class="intui"><b>同类推荐</b><a href="/biquge_273715/">苟在妖武乱世修仙</a><a href="/biquge_267955/">长生仙游</a><a href="/biquge_267432/">渊天尊</a><a href="/biquge_237316/">少年游</a><a href="/biquge_226192/">赤心巡天</a><a href="/biquge_268756/">人在死牢马甲成圣</a><a href="/biquge_279858/">山河志异</a><a href="/biquge_194051/">仙人,法力无边者为之</a><a href="/biquge_245211/">我用闲书成圣人</a><a href="/biquge_142308/">长生:我当了千年捕头</a><a href="/biquge_255977/">狂傲为仙</a><a href="/biquge_263597/">我是一个小卒</a><a href="/biquge_268223/">仙子,请听我解释</a><a href="/biquge_828/">万古仙穹</a><a href="/biquge_233036/">我在神雕修仙</a><a href="/biquge_262555/">大唐山海行</a><a href="/biquge_11194/">道君</a><a href="/biquge_177174/">万界女主掠夺系统</a><a href="/biquge_264762/">人间最高处</a><a href="/biquge_269034/">我自桃源来</a></div>
<p class="Readpage" id="bottom"><script>mulu();</script></p><p class="note"></p>
<p class="note"></p>
<script>list2();</script>
<footer>
<a href="#top"><img src="/images/icon-backtop.gif" title="↑" alt="↑"/></a>
<p class="version channel">
<a href="/">首页</a>
<a href="/mybook.php" >我的书架</a>
<a href="/newcase.html" >阅读记录</a>
<a href="/map/sitemap.xml" target="_blank">网站地图</a>
<div style='display:none'>
<script>tj();recordedclick(136435);</script></div>
</footer>
<script src="//msite.baidu.com/sdk/c.js?appid=1613540281162979"></script>
<script type="application/ld+json">
{
"@context": "https://ziyuan.baidu.com/contexts/cambrian.jsonld",
"@id": "http://m.gashuw.com/biquge_136435/",
"appid": "1613540281162979",
"title": "仙工开物免费阅读_仙工开物蛊真人最新章节列表_歌书网手机阅读",
"images": ["http://image.gashuw.com/136/136435/136435s.jpg"],
"pubDate": "2026-02-17T00:17:25"
}
</script>
<script>cambrian.render('tail')</script>
</body></html>
FILE:references/html_storage/bc4dacb45c3842ab2b1650c4cb33b4c8.meta.json
{
"url": "http://m.gashuw.com/biquge_136435/",
"size": 9861,
"timestamp": 1771399280.6727087,
"storage_path": "/workspace/projects/assets/html_storage/bc4dacb45c3842ab2b1650c4cb33b4c8.html"
}
FILE:references/html_storage/d6ecaea02c7055682b0e91da03dee196.html
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="application/xhtml+xml;charset=utf-8" />
<title>歌书网手机版手机站-玄幻小说_免费小说排行榜_好看的言情小说_都市小说阅读网_总裁文排行榜</title>
<meta name="generator" content="歌书网手机版手机站" />
<meta id="ctl00_metaKeywords" name="keywords" content="歌书网手机版手机站,玄幻奇幻,武侠仙侠,都市职业,历史军事,游戏竞技,同人美文,言情小说"/>
<meta id="ctl00_metaDescription" name="description" content="歌书网手机版手机站唯一官方网站。提供玄幻、武侠、原创、网游、都市、言情、科幻、恐怖、官场、穿越、重生等小说,首发小说最新章节免费,精彩尽在歌书网手机版手机站。" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no" />
<meta name="format-detection" content="telephone=no" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<link rel="stylesheet" href="/css/index.css" />
<script src="/js/wap.js"></script>
<script src="/js/gashuwcom.js"></script>
<style type="text/css">
.bannerLink .ajax_html, .list .ajax_html{height:auto; overflow:hidden; line-height:normal;}
.ajax_html{text-align:center; width:320px; margin:0 auto;}
.tabArea4 .ajax_html{width:25%; margin:0;}
.ajax_html img{padding:20px 0;}
.ajax_html b{display:none;}
</style>
</head>
<body>
<header class="Index_Header">
<img src="/images/logo.png" class="logo" title="歌书网手机版" alt="歌书网手机版"/>
<a href="/login.php" id="login" class="login">登录</a> </header>
<nav class="smallNav">
<a href="/sort/0_1/">分类</a>
<a href="/ph/week.html">排行</a>
<a href="/quanben.html">全本</a>
<a href="/newcase.html" style="width:32%">阅读记录</a>
<a href="/mybook.php">书架</a>
</nav>
<form name="articlesearch" class="searchForm" method="post" action="/s.php">
<input type="text" name="keyword" class="searchForm_input searchForm_input2" value="输入书名•作者" />
<input type="hidden" name="t" value="1" />
<input type="submit" class="searchForm_btn" value="搜索" />
</form><div id="center_tip"><b>最新网址:m.gashuw.com</b></div> <div class="tabArea tabArea4 hot">
<div class="tab-nav clearfix">
<h2><a class="tab-nav-cur">强推</a></h2>
<h2><a style="display: none;">热门</a></h2>
<h2><a style="display: none;">全本</a></h2>
<h2><a style="display: none;"> </a></h2>
</div>
<div class="slide">
<div class="slide-con4 div">
<div class="slide-item index_hot1">
<div class="hot_sale">
<a href="/biquge_136435/">
<img class="lazy" style="width:89px;height:119px;" data-original="http://image.gashuw.com/136/136435/136435s.jpg" />
<p class="title">仙工开物</p>
<p class="author">作者:蛊真人</p>
<p class="review">简介: 火山中,先贤大能遗留的机关仙宫,渴望着后继者。母亲舍命争取,获得仙宫宝印,临死前留给了宁拙。 我佛心魔印!渡己为佛,渡人成魔。...</p>
</a>
</div>
<div class="hot_sale">
<a href="/biquge_305437/">
<img class="lazy" style="width:89px;height:119px;" data-original="http://image.gashuw.com/305/305437/305437s.jpg" />
<p class="title">苟在初圣魔门当人材</p>
<p class="author">作者:鹤守月满池</p>
<p class="review">简介: 吕阳穿越修仙界,却成了魔门初圣宗的弟子。 幸得异宝【百世书】,死后可以重开一世,让一切从头再来,还能带回前世的宝物,修为,寿命...</p>
</a>
</div>
<div class="hot_sale">
<a href="/biquge_288659/">
<img class="lazy" style="width:89px;height:119px;" data-original="http://image.gashuw.com/288/288659/288659s.jpg" />
<p class="title">从婴儿开始入道</p>
<p class="author">作者:古羲</p>
<p class="review">简介: 乱世求生,盛世求名。饱经风霜的大禹朝仍处于盛世年间。西北,有少年徒步九千里,孤身斩大妖,争一世功名。 龙湖,有不世宗师赶赴人间...</p>
</a>
</div>
<div class="hot_sale">
<a href="/biquge_306364/">
<img class="lazy" style="width:89px;height:119px;" data-original="http://image.gashuw.com/images/nocover.jpg" />
<p class="title">玄鉴仙族</p>
<p class="author">作者:季越人</p>
<p class="review">简介: 陆江仙熬夜猝死,残魂却附在了一面满是裂痕的青灰色铜镜上,飘落到了浩瀚无垠的修仙世界。 凶险难测的大黎山,眉尺河旁小小的村落,一...</p>
</a>
</div>
<div class="hot_sale">
<a href="/biquge_306158/">
<img class="lazy" style="width:89px;height:119px;" data-original="http://image.gashuw.com/306/306158/306158s.jpg" />
<p class="title">重启人生</p>
<p class="author">作者:王梓钧</p>
<p class="review">简介: 当人生按下重启键,似乎一切都变得顺利起来……才怪咧!让我重生在高考之后多好啊,实在不行重生去读高一也可以。 重生在高三是什么鬼...</p>
</a>
</div>
<div class="hot_sale">
<a href="/biquge_298087/">
<img class="lazy" style="width:89px;height:119px;" data-original="http://image.gashuw.com/298/298087/298087s.jpg" />
<p class="title">夜无疆</p>
<p class="author">作者:辰东</p>
<p class="review">简介: 那一天太阳落下再也没有升起……...</p>
</a>
</div>
</div>
</div>
</div>
</div>
<div class="tabArea tabArea4 bannerLink div">
<div class="tab-nav tab-nav4 clearfix">
<h2><a class="tab-nav-cur">玄幻魔法</a></h2>
<h2><a style="display: none;"> </a></h2>
<h2><a style="display: none;"> </a></h2>
<h2><a style="display: none;"> </a></h2>
</div>
<div class="slide">
<div class="slide-con4">
<div class="slide-item index_sort1">
<ul> <li><p><span class="sort">[玄幻魔法]</span><span class="title"><a href="/biquge_267398/">女侠且慢</a></span></p></li><li><p><span class="sort">[玄幻魔法]</span><span class="title"><a href="/biquge_264274/">国王</a></span></p></li><li><p><span class="sort">[玄幻魔法]</span><span class="title"><a href="/biquge_268784/">大燕斩妖人</a></span></p></li><li><p><span class="sort">[玄幻魔法]</span><span class="title"><a href="/biquge_219053/">剑道第一仙</a></span></p></li><li><p><span class="sort">[玄幻魔法]</span><span class="title"><a href="/biquge_268859/">凌天剑帝</a></span></p></li><li><p><span class="sort">[玄幻魔法]</span><span class="title"><a href="/biquge_254542/">大道神主</a></span></p></li></ul>
</div>
</div>
</div>
</div>
<div class="tabArea tabArea4 bannerLink div">
<div class="tab-nav tab-nav4 clearfix">
<h2 id="H2_1"><a class="tab-nav-cur">修真小说</a></h2>
<h2><a style="display: none;"> </a></h2>
<h2><a style="display: none;"> </a></h2>
<h2><a style="display: none;"> </a></h2>
</div>
<div class="slide">
<div class="slide-con4">
<div class="slide-item index_sort1">
<ul> <li><p><span class="sort">[修真小说]</span><span class="title"><a href="/biquge_273715/">苟在妖武乱世修仙</a></span></p></li><li><p><span class="sort">[修真小说]</span><span class="title"><a href="/biquge_267955/">长生仙游</a></span></p></li><li><p><span class="sort">[修真小说]</span><span class="title"><a href="/biquge_267432/">渊天尊</a></span></p></li><li><p><span class="sort">[修真小说]</span><span class="title"><a href="/biquge_237316/">少年游</a></span></p></li><li><p><span class="sort">[修真小说]</span><span class="title"><a href="/biquge_226192/">赤心巡天</a></span></p></li><li><p><span class="sort">[修真小说]</span><span class="title"><a href="/biquge_268756/">人在死牢马甲成圣</a></span></p></li></ul>
</div>
</div>
</div>
</div>
<div class="tabArea tabArea4 bannerLink div">
<div class="tab-nav tab-nav4 clearfix">
<h2 id="H2_2"><a class="tab-nav-cur">都市小说</a></h2>
<h2><a style="display: none;"> </a></h2>
<h2><a style="display: none;"> </a></h2>
<h2><a style="display: none;"> </a></h2>
</div>
<div class="slide">
<div class="slide-con4">
<div class="slide-item index_sort1">
<ul> <li><p><span class="sort">[都市小说]</span><span class="title"><a href="/biquge_264079/">抱着日记去重生</a></span></p></li><li><p><span class="sort">[都市小说]</span><span class="title"><a href="/biquge_233875/">我竟然是仙二代</a></span></p></li><li><p><span class="sort">[都市小说]</span><span class="title"><a href="/biquge_170067/">春闺秘录:厂公太撩人</a></span></p></li><li><p><span class="sort">[都市小说]</span><span class="title"><a href="/biquge_250431/">我有七个倾城绝艳的师姐</a></span></p></li><li><p><span class="sort">[都市小说]</span><span class="title"><a href="/biquge_170673/">都市小医仙</a></span></p></li><li><p><span class="sort">[都市小说]</span><span class="title"><a href="/biquge_268404/">徒儿你无敌了,下山报仇去吧</a></span></p></li></ul>
</div>
</div>
</div>
</div>
<div class="tabArea tabArea4 bannerLink div">
<div class="tab-nav tab-nav4 clearfix">
<h2 id="H2_3"><a class="tab-nav-cur">历史军事</a></h2>
<h2><a style="display: none;"> </a></h2>
<h2><a style="display: none;"> </a></h2>
<h2><a style="display: none;"> </a></h2>
</div>
<div class="slide">
<div class="slide-con4">
<div class="slide-item index_sort1">
<ul> <li><p><span class="sort">[历史军事]</span><span class="title"><a href="/biquge_265962/">昏君开局:天下大乱,我落草为寇</a></span></p></li><li><p><span class="sort">[历史军事]</span><span class="title"><a href="/biquge_266153/">三国:开局献计曹操,成立摸金校尉</a></span></p></li><li><p><span class="sort">[历史军事]</span><span class="title"><a href="/biquge_260661/">初唐小卒</a></span></p></li><li><p><span class="sort">[历史军事]</span><span class="title"><a href="/biquge_266788/">北宋纨绔:开局狗头铡,包大人饶命</a></span></p></li><li><p><span class="sort">[历史军事]</span><span class="title"><a href="/biquge_261396/">藏武</a></span></p></li><li><p><span class="sort">[历史军事]</span><span class="title"><a href="/biquge_217297/">重生之大明国公</a></span></p></li></ul>
</div>
</div>
</div>
</div>
<div class="tabArea tabArea4 bannerLink div">
<div class="tab-nav tab-nav4 clearfix">
<h2 id="H2_2"><a class="tab-nav-cur">游戏小说</a></h2>
<h2><a style="display: none;"> </a></h2>
<h2><a style="display: none;"> </a></h2>
<h2><a style="display: none;"> </a></h2>
</div>
<div class="slide">
<div class="slide-con4">
<div class="slide-item index_sort1">
<ul> <li><p><span class="sort">[游戏小说]</span><span class="title"><a href="/biquge_268591/">我能召唤离谱伙伴</a></span></p></li><li><p><span class="sort">[游戏小说]</span><span class="title"><a href="/biquge_252605/">秦时之七剑传人</a></span></p></li><li><p><span class="sort">[游戏小说]</span><span class="title"><a href="/biquge_268258/">宇智波余孽被迫拯救忍界</a></span></p></li><li><p><span class="sort">[游戏小说]</span><span class="title"><a href="/biquge_267658/">斗罗之冰魔雨浩</a></span></p></li><li><p><span class="sort">[游戏小说]</span><span class="title"><a href="/biquge_268986/">宠魅之万兽之主</a></span></p></li><li><p><span class="sort">[游戏小说]</span><span class="title"><a href="/biquge_265125/">海贼:伟大航路上的技能大师</a></span></p></li></ul>
</div>
</div>
</div>
</div>
<script language="javascript" type="text/javascript" src="/js/zepto.min.js"></script>
<script language="javascript" type="text/javascript" src="/js/common.js"></script>
<script language="javascript" type="text/javascript" src="/js/lazyload.js"></script>
<script type="text/javascript" language="javascript">
$(function(){
$("img.lazy").lazyload();
});
</script>
<form name="articlesearch" class="searchForm" method="post" action="/s.php">
<input type="text" name="keyword" class="searchForm_input searchForm_input2" value="输入书名•作者" />
<input type="hidden" name="t" value="1" />
<input type="submit" class="searchForm_btn" value="搜索" />
</form>
<p class="note"></p>
<footer>
<a href="#top"><img src="/images/icon-backtop.gif" title="↑" alt="↑"/></a>
<p class="version channel">
<a href="/">首页</a>
<a href="/mybook.php" >我的书架</a>
<a href="/newcase.html" >阅读记录</a>
<a href="/map/sitemap.xml" target="_blank">网站地图</a>
<div style='display:none'>
<script>tj();login();</script>
</div>
</footer>
</body>
</html>
FILE:references/html_storage/d6ecaea02c7055682b0e91da03dee196.meta.json
{
"url": "http://m.gashuw.com",
"size": 13073,
"timestamp": 1771399047.7558467,
"storage_path": "/workspace/projects/assets/html_storage/d6ecaea02c7055682b0e91da03dee196.html"
}
FILE:references/knowledge_base/learning_stats/knowledge_index.json
{
"entries": {
"rule_书源规则:从入门到入土.md_1": {
"id": "rule_书源规则:从入门到入土.md_1",
"source_file": "书源规则:从入门到入土.md",
"type": "explanation",
"category": "general",
"title": "[]()Legado书源规则说明",
"tags": [
"rule",
"explanation",
"书源规则:从入门到入土_md"
]
},
"rule_书源规则:从入门到入土.md_3": {
"id": "rule_书源规则:从入门到入土.md_3",
"source_file": "书源规则:从入门到入土.md",
"type": "explanation",
"category": "general",
"title": "[]()概况",
"tags": [
"rule",
"explanation",
"书源规则:从入门到入土_md"
]
},
"rule_书源规则:从入门到入土.md_5": {
"id": "rule_书源规则:从入门到入土.md_5",
"source_file": "书源规则:从入门到入土.md",
"type": "explanation",
"category": "general",
"title": "[]()1、语法说明",
"tags": [
"rule",
"explanation",
"书源规则:从入门到入土_md"
]
},
"rule_书源规则:从入门到入土.md_7": {
"id": "rule_书源规则:从入门到入土.md_7",
"source_file": "书源规则:从入门到入土.md",
"type": "explanation",
"category": "general",
"title": "[]()2、Legado的特殊规则",
"tags": [
"rule",
"explanation",
"书源规则:从入门到入土_md"
]
},
"rule_书源规则:从入门到入土.md_9": {
"id": "rule_书源规则:从入门到入土.md_9",
"source_file": "书源规则:从入门到入土.md",
"type": "explanation",
"category": "general",
"title": "[]()3、书源之「基本」",
"tags": [
"rule",
"explanation",
"书源规则:从入门到入土_md"
]
},
"rule_书源规则:从入门到入土.md_11": {
"id": "rule_书源规则:从入门到入土.md_11",
"source_file": "书源规则:从入门到入土.md",
"type": "explanation",
"category": "general",
"title": "[]()4、书源之「搜索」",
"tags": [
"rule",
"explanation",
"书源规则:从入门到入土_md"
]
},
"rule_书源规则:从入门到入土.md_13": {
"id": "rule_书源规则:从入门到入土.md_13",
"source_file": "书源规则:从入门到入土.md",
"type": "explanation",
"category": "general",
"title": "[]()5、书源之「发现」",
"tags": [
"rule",
"explanation",
"书源规则:从入门到入土_md"
]
},
"rule_书源规则:从入门到入土.md_15": {
"id": "rule_书源规则:从入门到入土.md_15",
"source_file": "书源规则:从入门到入土.md",
"type": "explanation",
"category": "general",
"title": "[]()6、书源之「详情」",
"tags": [
"rule",
"explanation",
"书源规则:从入门到入土_md"
]
},
"rule_书源规则:从入门到入土.md_17": {
"id": "rule_书源规则:从入门到入土.md_17",
"source_file": "书源规则:从入门到入土.md",
"type": "explanation",
"category": "general",
"title": "[]()7、书源之「目录」",
"tags": [
"rule",
"explanation",
"书源规则:从入门到入土_md"
]
},
"rule_书源规则:从入门到入土.md_19": {
"id": "rule_书源规则:从入门到入土.md_19",
"source_file": "书源规则:从入门到入土.md",
"type": "explanation",
"category": "general",
"title": "[]()8、书源之「正文」",
"tags": [
"rule",
"explanation",
"书源规则:从入门到入土_md"
]
},
"rule_书源规则:从入门到入土.md_21": {
"id": "rule_书源规则:从入门到入土.md_21",
"source_file": "书源规则:从入门到入土.md",
"type": "explanation",
"category": "general",
"title": "[]()9、补充说明",
"tags": [
"rule",
"explanation",
"书源规则:从入门到入土_md"
]
},
"rule_legado_knowledge_base.md_1": {
"id": "rule_legado_knowledge_base.md_1",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "Legado书源知识库 - 完整版",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_5": {
"id": "rule_legado_knowledge_base.md_5",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "1. BookSource(书源主类)",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_7": {
"id": "rule_legado_knowledge_base.md_7",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "2. SearchRule(搜索规则)",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_9": {
"id": "rule_legado_knowledge_base.md_9",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "3. BookInfoRule(书籍信息规则)",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_11": {
"id": "rule_legado_knowledge_base.md_11",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "4. TocRule(目录规则)",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_13": {
"id": "rule_legado_knowledge_base.md_13",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "5. ContentRule(正文规则)",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_15": {
"id": "rule_legado_knowledge_base.md_15",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "6. ExploreRule(发现规则)",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_17": {
"id": "rule_legado_knowledge_base.md_17",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "7. Book(书籍实体)",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_19": {
"id": "rule_legado_knowledge_base.md_19",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "8. BookChapter(章节实体)",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_23": {
"id": "rule_legado_knowledge_base.md_23",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "1. 基本语法格式",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_27": {
"id": "rule_legado_knowledge_base.md_27",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "@text - 提取所有文本",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_29": {
"id": "rule_legado_knowledge_base.md_29",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "@ownText - 只提取当前元素的文本",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_31": {
"id": "rule_legado_knowledge_base.md_31",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "@html - 提取完整HTML结构",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_33": {
"id": "rule_legado_knowledge_base.md_33",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "@textNode - 提取文本节点",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_35": {
"id": "rule_legado_knowledge_base.md_35",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "@属性名 - 提取属性值",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_39": {
"id": "rule_legado_knowledge_base.md_39",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "元素选择器",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_41": {
"id": "rule_legado_knowledge_base.md_41",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "类选择器",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_43": {
"id": "rule_legado_knowledge_base.md_43",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "ID选择器",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_45": {
"id": "rule_legado_knowledge_base.md_45",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "属性选择器",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_49": {
"id": "rule_legado_knowledge_base.md_49",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "后代选择器",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_51": {
"id": "rule_legado_knowledge_base.md_51",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "子元素选择器",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_53": {
"id": "rule_legado_knowledge_base.md_53",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "相邻兄弟选择器",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_55": {
"id": "rule_legado_knowledge_base.md_55",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "通用兄弟选择器",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_59": {
"id": "rule_legado_knowledge_base.md_59",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "位置伪类",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_61": {
"id": "rule_legado_knowledge_base.md_61",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "状态伪类",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_63": {
"id": "rule_legado_knowledge_base.md_63",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "内容伪类",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_67": {
"id": "rule_legado_knowledge_base.md_67",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "基本替换",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_69": {
"id": "rule_legado_knowledge_base.md_69",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "替换为指定内容",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_71": {
"id": "rule_legado_knowledge_base.md_71",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "提取匹配内容",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_75": {
"id": "rule_legado_knowledge_base.md_75",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "书籍列表页",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_77": {
"id": "rule_legado_knowledge_base.md_77",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "正文内容页",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_79": {
"id": "rule_legado_knowledge_base.md_79",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "书籍详情页",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_83": {
"id": "rule_legado_knowledge_base.md_83",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "1. 基本格式",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_87": {
"id": "rule_legado_knowledge_base.md_87",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "class - 按class选择",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_89": {
"id": "rule_legado_knowledge_base.md_89",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "id - 按id选择",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_91": {
"id": "rule_legado_knowledge_base.md_91",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "tag - 按标签选择",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_93": {
"id": "rule_legado_knowledge_base.md_93",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "text - 按文本内容选择",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_95": {
"id": "rule_legado_knowledge_base.md_95",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "children - 获取所有子标签",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_99": {
"id": "rule_legado_knowledge_base.md_99",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "正数位置",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_101": {
"id": "rule_legado_knowledge_base.md_101",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "负数位置",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_103": {
"id": "rule_legado_knowledge_base.md_103",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "排除位置",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_105": {
"id": "rule_legado_knowledge_base.md_105",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "区间位置",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_107": {
"id": "rule_legado_knowledge_base.md_107",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "步长位置",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_111": {
"id": "rule_legado_knowledge_base.md_111",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "提取第一个元素",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_113": {
"id": "rule_legado_knowledge_base.md_113",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "提取最后一个元素",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_115": {
"id": "rule_legado_knowledge_base.md_115",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "提取指定范围",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_117": {
"id": "rule_legado_knowledge_base.md_117",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "组合使用",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_123": {
"id": "rule_legado_knowledge_base.md_123",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "绝对路径",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_125": {
"id": "rule_legado_knowledge_base.md_125",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "相对路径",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_129": {
"id": "rule_legado_knowledge_base.md_129",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "ancestor - 祖先节点",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_131": {
"id": "rule_legado_knowledge_base.md_131",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "descendant - 后代节点",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_133": {
"id": "rule_legado_knowledge_base.md_133",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "following - 后面的节点",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_135": {
"id": "rule_legado_knowledge_base.md_135",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "preceding - 前面的节点",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_139": {
"id": "rule_legado_knowledge_base.md_139",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "text() - 提取文本",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_141": {
"id": "rule_legado_knowledge_base.md_141",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "contains() - 包含匹配",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_143": {
"id": "rule_legado_knowledge_base.md_143",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "starts-with() - 以...开头",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_145": {
"id": "rule_legado_knowledge_base.md_145",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "position() - 位置",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_147": {
"id": "rule_legado_knowledge_base.md_147",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "last() - 最后一个",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_149": {
"id": "rule_legado_knowledge_base.md_149",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "4. 属性选择",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_153": {
"id": "rule_legado_knowledge_base.md_153",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "提取书名",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_155": {
"id": "rule_legado_knowledge_base.md_155",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "提取链接",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_157": {
"id": "rule_legado_knowledge_base.md_157",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "提取包含特定文本的元素",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_159": {
"id": "rule_legado_knowledge_base.md_159",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "提取第3个章节",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_163": {
"id": "rule_legado_knowledge_base.md_163",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "1. 基本语法",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_167": {
"id": "rule_legado_knowledge_base.md_167",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "根节点",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_169": {
"id": "rule_legado_knowledge_base.md_169",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "子节点",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_171": {
"id": "rule_legado_knowledge_base.md_171",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "数组索引",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_173": {
"id": "rule_legado_knowledge_base.md_173",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "通配符",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_175": {
"id": "rule_legado_knowledge_base.md_175",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "递归搜索",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_179": {
"id": "rule_legado_knowledge_base.md_179",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "提取数组中的第一个元素",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_181": {
"id": "rule_legado_knowledge_base.md_181",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "提取数组中的所有标题",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_183": {
"id": "rule_legado_knowledge_base.md_183",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "提取符合条件的元素",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_189": {
"id": "rule_legado_knowledge_base.md_189",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "字符类",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_191": {
"id": "rule_legado_knowledge_base.md_191",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "量词",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_193": {
"id": "rule_legado_knowledge_base.md_193",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "特殊字符",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_197": {
"id": "rule_legado_knowledge_base.md_197",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "匹配章节标题",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_199": {
"id": "rule_legado_knowledge_base.md_199",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "匹配日期",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_201": {
"id": "rule_legado_knowledge_base.md_201",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "匹配URL",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_203": {
"id": "rule_legado_knowledge_base.md_203",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "匹配手机号",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_205": {
"id": "rule_legado_knowledge_base.md_205",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "3. 在规则中的应用",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_211": {
"id": "rule_legado_knowledge_base.md_211",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "JavaScript引擎",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_213": {
"id": "rule_legado_knowledge_base.md_213",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "变量声明注意事项",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_215": {
"id": "rule_legado_knowledge_base.md_215",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "Java类调用",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_217": {
"id": "rule_legado_knowledge_base.md_217",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "安全限制",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_219": {
"id": "rule_legado_knowledge_base.md_219",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "2. 基本语法",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_221": {
"id": "rule_legado_knowledge_base.md_221",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "3. 核心变量",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_227": {
"id": "rule_legado_knowledge_base.md_227",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "ajax - 访问网络",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_229": {
"id": "rule_legado_knowledge_base.md_229",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "ajaxAll - 并发访问多个网络地址",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_231": {
"id": "rule_legado_knowledge_base.md_231",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "ajaxTestAll - 测试多个网络地址",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_233": {
"id": "rule_legado_knowledge_base.md_233",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "connect - 访问网络,返回StrResponse对象",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_235": {
"id": "rule_legado_knowledge_base.md_235",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "get、head、post",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_237": {
"id": "rule_legado_knowledge_base.md_237",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "webView - 使用webView访问网络",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_239": {
"id": "rule_legado_knowledge_base.md_239",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "webViewGetOverrideUrl - 使用webView获取跳转url",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_241": {
"id": "rule_legado_knowledge_base.md_241",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "webViewGetSource - 使用webView获取资源url",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_243": {
"id": "rule_legado_knowledge_base.md_243",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "startBrowser - 使用内置浏览器打开链接",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_245": {
"id": "rule_legado_knowledge_base.md_245",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "startBrowserAwait - 使用内置浏览器打开链接并等待结果",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_249": {
"id": "rule_legado_knowledge_base.md_249",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "getString - 获取文本",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_251": {
"id": "rule_legado_knowledge_base.md_251",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "getStringList - 获取文本列表",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_253": {
"id": "rule_legado_knowledge_base.md_253",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "setContent - 设置解析内容",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_255": {
"id": "rule_legado_knowledge_base.md_255",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "getElement - 获取Element",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_257": {
"id": "rule_legado_knowledge_base.md_257",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "getElements - 获取Element列表",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_261": {
"id": "rule_legado_knowledge_base.md_261",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "downloadFile - 下载文件",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_263": {
"id": "rule_legado_knowledge_base.md_263",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "readTxtFile - 读取文本文件",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_265": {
"id": "rule_legado_knowledge_base.md_265",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "deleteFile - 删除文件",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_267": {
"id": "rule_legado_knowledge_base.md_267",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "unArchiveFile - 解压压缩文件",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_269": {
"id": "rule_legado_knowledge_base.md_269",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "getTxtInFolder - 读取文件夹内所有文本文件",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_271": {
"id": "rule_legado_knowledge_base.md_271",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "getZipStringContent - 获取压缩文件内指定文件的内容",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_275": {
"id": "rule_legado_knowledge_base.md_275",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "importScript - 导入JavaScript脚本",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_277": {
"id": "rule_legado_knowledge_base.md_277",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "cacheFile - 缓存网络文件",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_281": {
"id": "rule_legado_knowledge_base.md_281",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "base64编码解码",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_283": {
"id": "rule_legado_knowledge_base.md_283",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "Hex编码解码",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_285": {
"id": "rule_legado_knowledge_base.md_285",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "ByteArray转换",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_287": {
"id": "rule_legado_knowledge_base.md_287",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "URI编码",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_289": {
"id": "rule_legado_knowledge_base.md_289",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "繁简转换",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_293": {
"id": "rule_legado_knowledge_base.md_293",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "timeFormat - 时间格式化",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_295": {
"id": "rule_legado_knowledge_base.md_295",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "timeFormatUTC - UTC时间格式化",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_299": {
"id": "rule_legado_knowledge_base.md_299",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "htmlFormat - HTML格式化",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_301": {
"id": "rule_legado_knowledge_base.md_301",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "toast - 弹窗提示",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_303": {
"id": "rule_legado_knowledge_base.md_303",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "log - 日志输出",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_305": {
"id": "rule_legado_knowledge_base.md_305",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "getVerificationCode - 获取用户输入的验证码",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_307": {
"id": "rule_legado_knowledge_base.md_307",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "getWebViewUA - 获取SystemWebView User-Agent",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_309": {
"id": "rule_legado_knowledge_base.md_309",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "randomUUID - 生成UUID",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_311": {
"id": "rule_legado_knowledge_base.md_311",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "androidId - 获取Android ID",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_313": {
"id": "rule_legado_knowledge_base.md_313",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "openUrl - 跳转外部链接/应用",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_315": {
"id": "rule_legado_knowledge_base.md_315",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "openVideoPlayer - 打开视频播放器",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_319": {
"id": "rule_legado_knowledge_base.md_319",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "5.1 属性",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_323": {
"id": "rule_legado_knowledge_base.md_323",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "变量存取",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_327": {
"id": "rule_legado_knowledge_base.md_327",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "6.1 属性",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_331": {
"id": "rule_legado_knowledge_base.md_331",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "变量存取",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_333": {
"id": "rule_legado_knowledge_base.md_333",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "存储额外信息",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_339": {
"id": "rule_legado_knowledge_base.md_339",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "获取书源URL",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_341": {
"id": "rule_legado_knowledge_base.md_341",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "变量存取",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_343": {
"id": "rule_legado_knowledge_base.md_343",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "登录头操作",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_345": {
"id": "rule_legado_knowledge_base.md_345",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "登录信息操作",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_347": {
"id": "rule_legado_knowledge_base.md_347",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "书源缓存刷新",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_349": {
"id": "rule_legado_knowledge_base.md_349",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "8. cookie对象",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_351": {
"id": "rule_legado_knowledge_base.md_351",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "9. cache对象",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_355": {
"id": "rule_legado_knowledge_base.md_355",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "10.1 对称加密",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_357": {
"id": "rule_legado_knowledge_base.md_357",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "10.2 非对称加密",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_359": {
"id": "rule_legado_knowledge_base.md_359",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "10.3 签名",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_361": {
"id": "rule_legado_knowledge_base.md_361",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "10.4 摘要",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_363": {
"id": "rule_legado_knowledge_base.md_363",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "10.5 MD5",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_365": {
"id": "rule_legado_knowledge_base.md_365",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "10.6 HMac",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_369": {
"id": "rule_legado_knowledge_base.md_369",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "11.1 使用ajax获取内容",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_371": {
"id": "rule_legado_knowledge_base.md_371",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "11.2 使用connect获取响应对象",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_373": {
"id": "rule_legado_knowledge_base.md_373",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "11.3 使用base64解码",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_375": {
"id": "rule_legado_knowledge_base.md_375",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "11.4 使用正则处理内容",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_377": {
"id": "rule_legado_knowledge_base.md_377",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "11.5 加密请求参数",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_379": {
"id": "rule_legado_knowledge_base.md_379",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "11.6 繁简转换",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_381": {
"id": "rule_legado_knowledge_base.md_381",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "11.7 使用book对象",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_383": {
"id": "rule_legado_knowledge_base.md_383",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "11.8 使用chapter对象",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_387": {
"id": "rule_legado_knowledge_base.md_387",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "1. 基本格式",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_391": {
"id": "rule_legado_knowledge_base.md_391",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "User-Agent",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_393": {
"id": "rule_legado_knowledge_base.md_393",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "Referer",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_395": {
"id": "rule_legado_knowledge_base.md_395",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "Cookie",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_397": {
"id": "rule_legado_knowledge_base.md_397",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "Accept",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_399": {
"id": "rule_legado_knowledge_base.md_399",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "3. 在规则中的应用",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_403": {
"id": "rule_legado_knowledge_base.md_403",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "1. 为什么需要动态加载",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_407": {
"id": "rule_legado_knowledge_base.md_407",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "方法一:直接添加webView参数",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_409": {
"id": "rule_legado_knowledge_base.md_409",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "方法二:正则替换",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_411": {
"id": "rule_legado_knowledge_base.md_411",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "方法三:JS拼接",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_413": {
"id": "rule_legado_knowledge_base.md_413",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "方法四:利用{{}}拼接",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_415": {
"id": "rule_legado_knowledge_base.md_415",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "3. 注意事项",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_419": {
"id": "rule_legado_knowledge_base.md_419",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "1. 适用场景",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_421": {
"id": "rule_legado_knowledge_base.md_421",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "2. 可用方法",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_425": {
"id": "rule_legado_knowledge_base.md_425",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "检查登录状态并重新访问",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_431": {
"id": "rule_legado_knowledge_base.md_431",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "网页类型",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_433": {
"id": "rule_legado_knowledge_base.md_433",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "图片类型",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_435": {
"id": "rule_legado_knowledge_base.md_435",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "视频类型",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_439": {
"id": "rule_legado_knowledge_base.md_439",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "sourceUrl - 源URL(必填)",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_441": {
"id": "rule_legado_knowledge_base.md_441",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "sourceName - 源名称(必填)",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_443": {
"id": "rule_legado_knowledge_base.md_443",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "sourceIcon - 源图标(可选)",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_445": {
"id": "rule_legado_knowledge_base.md_445",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "sourceGroup - 源分组(可选)",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_449": {
"id": "rule_legado_knowledge_base.md_449",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "ruleArticles - 列表规则",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_451": {
"id": "rule_legado_knowledge_base.md_451",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "ruleTitle - 标题规则",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_453": {
"id": "rule_legado_knowledge_base.md_453",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "ruleLink - 链接规则(必填)",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_455": {
"id": "rule_legado_knowledge_base.md_455",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "rulePubDate - 时间规则",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_457": {
"id": "rule_legado_knowledge_base.md_457",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "ruleDescription - 描述规则",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_459": {
"id": "rule_legado_knowledge_base.md_459",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "ruleContent - 内容规则",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_461": {
"id": "rule_legado_knowledge_base.md_461",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "ruleImage - 图片url规则",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_463": {
"id": "rule_legado_knowledge_base.md_463",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "4. 订阅源示例",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_467": {
"id": "rule_legado_knowledge_base.md_467",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "1. CloudFlare验证问题",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_469": {
"id": "rule_legado_knowledge_base.md_469",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "2. 内容显示\"加载中\"",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_471": {
"id": "rule_legado_knowledge_base.md_471",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "3. 目录顺序是乱的",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_473": {
"id": "rule_legado_knowledge_base.md_473",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "4. 正文里有广告",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_475": {
"id": "rule_legado_knowledge_base.md_475",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "5. 需要分页",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_477": {
"id": "rule_legado_knowledge_base.md_477",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "6. 图片防盗链",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_479": {
"id": "rule_legado_knowledge_base.md_479",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "7. POST请求",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_483": {
"id": "rule_legado_knowledge_base.md_483",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "1. 选择器选择优先级",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_485": {
"id": "rule_legado_knowledge_base.md_485",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "2. 提取类型选择",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_487": {
"id": "rule_legado_knowledge_base.md_487",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "3. 性能优化",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_489": {
"id": "rule_legado_knowledge_base.md_489",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "4. 规则调试",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_491": {
"id": "rule_legado_knowledge_base.md_491",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "5. 代码规范",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_493": {
"id": "rule_legado_knowledge_base.md_493",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "6. 安全注意事项",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_495": {
"id": "rule_legado_knowledge_base.md_495",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "十三、规则标志速查",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_497": {
"id": "rule_legado_knowledge_base.md_497",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "十四、完整书源JSON模板",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_501": {
"id": "rule_legado_knowledge_base.md_501",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "常用CSS选择器",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_503": {
"id": "rule_legado_knowledge_base.md_503",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "常用提取类型",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_505": {
"id": "rule_legado_knowledge_base.md_505",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "常用正则表达式",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_507": {
"id": "rule_legado_knowledge_base.md_507",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "常用JavaScript函数",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_511": {
"id": "rule_legado_knowledge_base.md_511",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "1. 基本概念",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_515": {
"id": "rule_legado_knowledge_base.md_515",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "标准格式",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_517": {
"id": "rule_legado_knowledge_base.md_517",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "选择器类型",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_521": {
"id": "rule_legado_knowledge_base.md_521",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "@text - 文本内容",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_523": {
"id": "rule_legado_knowledge_base.md_523",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "@ownText - 自身文本",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_525": {
"id": "rule_legado_knowledge_base.md_525",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "@html - HTML结构",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_527": {
"id": "rule_legado_knowledge_base.md_527",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "@属性名 - 属性值",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_531": {
"id": "rule_legado_knowledge_base.md_531",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "基本替换",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_533": {
"id": "rule_legado_knowledge_base.md_533",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "删除匹配",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_535": {
"id": "rule_legado_knowledge_base.md_535",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "替换内容",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_539": {
"id": "rule_legado_knowledge_base.md_539",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "多重替换",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_541": {
"id": "rule_legado_knowledge_base.md_541",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "正则分组",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_543": {
"id": "rule_legado_knowledge_base.md_543",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "贪婪匹配",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_547": {
"id": "rule_legado_knowledge_base.md_547",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "提取书名",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_549": {
"id": "rule_legado_knowledge_base.md_549",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "提取作者",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_551": {
"id": "rule_legado_knowledge_base.md_551",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "提取简介",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_553": {
"id": "rule_legado_knowledge_base.md_553",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "提取封面",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_557": {
"id": "rule_legado_knowledge_base.md_557",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "1. 基本概念",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_561": {
"id": "rule_legado_knowledge_base.md_561",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "元素选择",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_563": {
"id": "rule_legado_knowledge_base.md_563",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "属性选择",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_565": {
"id": "rule_legado_knowledge_base.md_565",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "文本选择",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_569": {
"id": "rule_legado_knowledge_base.md_569",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "绝对路径",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_571": {
"id": "rule_legado_knowledge_base.md_571",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "相对路径",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_573": {
"id": "rule_legado_knowledge_base.md_573",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "通配符",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_577": {
"id": "rule_legado_knowledge_base.md_577",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "子节点",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_579": {
"id": "rule_legado_knowledge_base.md_579",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "父节点",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_581": {
"id": "rule_legado_knowledge_base.md_581",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "祖先节点",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_583": {
"id": "rule_legado_knowledge_base.md_583",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "后代节点",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_585": {
"id": "rule_legado_knowledge_base.md_585",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "兄弟节点",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_589": {
"id": "rule_legado_knowledge_base.md_589",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "位置选择",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_591": {
"id": "rule_legado_knowledge_base.md_591",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "属性选择",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_593": {
"id": "rule_legado_knowledge_base.md_593",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "条件组合",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_597": {
"id": "rule_legado_knowledge_base.md_597",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "文本函数",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_599": {
"id": "rule_legado_knowledge_base.md_599",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "字符串函数",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_601": {
"id": "rule_legado_knowledge_base.md_601",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "数值函数",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_605": {
"id": "rule_legado_knowledge_base.md_605",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "提取书名",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_607": {
"id": "rule_legado_knowledge_base.md_607",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "提取作者",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_609": {
"id": "rule_legado_knowledge_base.md_609",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "提取链接",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_611": {
"id": "rule_legado_knowledge_base.md_611",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "提取所有章节",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_615": {
"id": "rule_legado_knowledge_base.md_615",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "1. 基本概念",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_619": {
"id": "rule_legado_knowledge_base.md_619",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "根节点",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_621": {
"id": "rule_legado_knowledge_base.md_621",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "当前节点",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_623": {
"id": "rule_legado_knowledge_base.md_623",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "数组索引",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_625": {
"id": "rule_legado_knowledge_base.md_625",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "数组切片",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_629": {
"id": "rule_legado_knowledge_base.md_629",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "子节点",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_631": {
"id": "rule_legado_knowledge_base.md_631",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "递归查找",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_633": {
"id": "rule_legado_knowledge_base.md_633",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "通配符",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_635": {
"id": "rule_legado_knowledge_base.md_635",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "脚本表达式",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_639": {
"id": "rule_legado_knowledge_base.md_639",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "比较操作",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_641": {
"id": "rule_legado_knowledge_base.md_641",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "逻辑操作",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_643": {
"id": "rule_legado_knowledge_base.md_643",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "存在性检查",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_647": {
"id": "rule_legado_knowledge_base.md_647",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "提取书名",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_649": {
"id": "rule_legado_knowledge_base.md_649",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "提取作者",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_651": {
"id": "rule_legado_knowledge_base.md_651",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "提取章节列表",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_653": {
"id": "rule_legado_knowledge_base.md_653",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "提取封面URL",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_659": {
"id": "rule_legado_knowledge_base.md_659",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "字符类",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_661": {
"id": "rule_legado_knowledge_base.md_661",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "量词",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_663": {
"id": "rule_legado_knowledge_base.md_663",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "边界",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_667": {
"id": "rule_legado_knowledge_base.md_667",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "提取章节号",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_669": {
"id": "rule_legado_knowledge_base.md_669",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "提取日期",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_671": {
"id": "rule_legado_knowledge_base.md_671",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "提取URL",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_673": {
"id": "rule_legado_knowledge_base.md_673",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "去除HTML标签",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_677": {
"id": "rule_legado_knowledge_base.md_677",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "分组",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_679": {
"id": "rule_legado_knowledge_base.md_679",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "引用",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_681": {
"id": "rule_legado_knowledge_base.md_681",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "非捕获分组",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_685": {
"id": "rule_legado_knowledge_base.md_685",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "清理广告",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_687": {
"id": "rule_legado_knowledge_base.md_687",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "提取纯文本",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_689": {
"id": "rule_legado_knowledge_base.md_689",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "提取数字",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_691": {
"id": "rule_legado_knowledge_base.md_691",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "格式化文本",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_695": {
"id": "rule_legado_knowledge_base.md_695",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "1. 基本概念",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_699": {
"id": "rule_legado_knowledge_base.md_699",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "book对象 - 书籍信息",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_701": {
"id": "rule_legado_knowledge_base.md_701",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "chapter对象 - 章节信息",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_703": {
"id": "rule_legado_knowledge_base.md_703",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "source对象 - 书源信息",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_705": {
"id": "rule_legado_knowledge_base.md_705",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "cache对象 - 缓存数据",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_709": {
"id": "rule_legado_knowledge_base.md_709",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "HTTP请求",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_711": {
"id": "rule_legado_knowledge_base.md_711",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "字符串处理",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_713": {
"id": "rule_legado_knowledge_base.md_713",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "时间处理",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_715": {
"id": "rule_legado_knowledge_base.md_715",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "JSON处理",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_717": {
"id": "rule_legado_knowledge_base.md_717",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "4. 正则表达式",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_721": {
"id": "rule_legado_knowledge_base.md_721",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "动态生成URL",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_723": {
"id": "rule_legado_knowledge_base.md_723",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "处理加密数据",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_725": {
"id": "rule_legado_knowledge_base.md_725",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "提取复杂结构",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_731": {
"id": "rule_legado_knowledge_base.md_731",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "设置请求头",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_733": {
"id": "rule_legado_knowledge_base.md_733",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "常用请求头",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_737": {
"id": "rule_legado_knowledge_base.md_737",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "Cookie配置",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_739": {
"id": "rule_legado_knowledge_base.md_739",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "并发控制",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_741": {
"id": "rule_legado_knowledge_base.md_741",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "超时配置",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_745": {
"id": "rule_legado_knowledge_base.md_745",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "反爬虫应对",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_747": {
"id": "rule_legado_knowledge_base.md_747",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "登录状态保持",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_753": {
"id": "rule_legado_knowledge_base.md_753",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "问题原因",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_755": {
"id": "rule_legado_knowledge_base.md_755",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "解决方案",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_759": {
"id": "rule_legado_knowledge_base.md_759",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "问题原因",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_761": {
"id": "rule_legado_knowledge_base.md_761",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "解决方案",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_765": {
"id": "rule_legado_knowledge_base.md_765",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "问题原因",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_767": {
"id": "rule_legado_knowledge_base.md_767",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "解决方案",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_771": {
"id": "rule_legado_knowledge_base.md_771",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "问题原因",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_773": {
"id": "rule_legado_knowledge_base.md_773",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "解决方案",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_779": {
"id": "rule_legado_knowledge_base.md_779",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "优先使用稳定的选择器",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_781": {
"id": "rule_legado_knowledge_base.md_781",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "使用后代选择器而非标签选择器",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_785": {
"id": "rule_legado_knowledge_base.md_785",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "使用非贪婪匹配",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_787": {
"id": "rule_legado_knowledge_base.md_787",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "避免复杂的回溯",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_791": {
"id": "rule_legado_knowledge_base.md_791",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "减少不必要的请求",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_793": {
"id": "rule_legado_knowledge_base.md_793",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "使用缓存",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_797": {
"id": "rule_legado_knowledge_base.md_797",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "添加注释",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_799": {
"id": "rule_legado_knowledge_base.md_799",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "统一命名",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_805": {
"id": "rule_legado_knowledge_base.md_805",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "启用调试模式",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_807": {
"id": "rule_legado_knowledge_base.md_807",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "查看调试日志",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_811": {
"id": "rule_legado_knowledge_base.md_811",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "测试URL",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_813": {
"id": "rule_legado_knowledge_base.md_813",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "测试请求",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_815": {
"id": "rule_legado_knowledge_base.md_815",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "测试解析",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_817": {
"id": "rule_legado_knowledge_base.md_817",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "3. 常用调试函数",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_821": {
"id": "rule_legado_knowledge_base.md_821",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "1. 简单文本书源",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_823": {
"id": "rule_legado_knowledge_base.md_823",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "2. 复杂JSON书源",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_825": {
"id": "rule_legado_knowledge_base.md_825",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "3. 动态加载书源",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_829": {
"id": "rule_legado_knowledge_base.md_829",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "常用选择器速查",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_831": {
"id": "rule_legado_knowledge_base.md_831",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "常用正则表达式速查",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_legado_knowledge_base.md_833": {
"id": "rule_legado_knowledge_base.md_833",
"source_file": "legado_knowledge_base.md",
"type": "explanation",
"category": "general",
"title": "常用JavaScript函数速查",
"tags": [
"rule",
"explanation",
"legado_knowledge_base_md"
]
},
"rule_订阅源规则:从入门到再入门 (4).md_1": {
"id": "rule_订阅源规则:从入门到再入门 (4).md_1",
"source_file": "订阅源规则:从入门到再入门 (4).md",
"type": "explanation",
"category": "general",
"title": "[]()Legado订阅源规则说明",
"tags": [
"rule",
"explanation",
"订阅源规则:从入门到再入门 (4)_md"
]
},
"rule_订阅源规则:从入门到再入门 (4).md_3": {
"id": "rule_订阅源规则:从入门到再入门 (4).md_3",
"source_file": "订阅源规则:从入门到再入门 (4).md",
"type": "explanation",
"category": "general",
"title": "[]()概况",
"tags": [
"rule",
"explanation",
"订阅源规则:从入门到再入门 (4)_md"
]
},
"rule_订阅源规则:从入门到再入门 (4).md_5": {
"id": "rule_订阅源规则:从入门到再入门 (4).md_5",
"source_file": "订阅源规则:从入门到再入门 (4).md",
"type": "explanation",
"category": "general",
"title": "[]()1、语法说明",
"tags": [
"rule",
"explanation",
"订阅源规则:从入门到再入门 (4)_md"
]
},
"rule_订阅源规则:从入门到再入门 (4).md_7": {
"id": "rule_订阅源规则:从入门到再入门 (4).md_7",
"source_file": "订阅源规则:从入门到再入门 (4).md",
"type": "explanation",
"category": "general",
"title": "[]()2、Legado的特殊规则",
"tags": [
"rule",
"explanation",
"订阅源规则:从入门到再入门 (4)_md"
]
},
"rule_订阅源规则:从入门到再入门 (4).md_9": {
"id": "rule_订阅源规则:从入门到再入门 (4).md_9",
"source_file": "订阅源规则:从入门到再入门 (4).md",
"type": "explanation",
"category": "general",
"title": "[]()3、解析流程",
"tags": [
"rule",
"explanation",
"订阅源规则:从入门到再入门 (4)_md"
]
},
"rule_订阅源规则:从入门到再入门 (4).md_11": {
"id": "rule_订阅源规则:从入门到再入门 (4).md_11",
"source_file": "订阅源规则:从入门到再入门 (4).md",
"type": "explanation",
"category": "general",
"title": "[]()4、规则概述",
"tags": [
"rule",
"explanation",
"订阅源规则:从入门到再入门 (4)_md"
]
},
"rule_订阅源规则:从入门到再入门 (4).md_13": {
"id": "rule_订阅源规则:从入门到再入门 (4).md_13",
"source_file": "订阅源规则:从入门到再入门 (4).md",
"type": "explanation",
"category": "general",
"title": "[]()5、附录",
"tags": [
"rule",
"explanation",
"订阅源规则:从入门到再入门 (4)_md"
]
},
"rule_订阅源规则帮助.txt_1": {
"id": "rule_订阅源规则帮助.txt_1",
"source_file": "订阅源规则帮助.txt",
"type": "explanation",
"category": "general",
"title": "订阅源规则帮助",
"tags": [
"rule",
"explanation",
"订阅源规则帮助_txt"
]
},
"css_1": {
"id": "css_1",
"source_file": "css选择器规则.txt",
"type": "explanation",
"category": "css",
"title": "核心速览",
"tags": [
"css",
"selector",
"核心速览"
]
},
"css_5": {
"id": "css_5",
"source_file": "css选择器规则.txt",
"type": "explanation",
"category": "css",
"title": "1. 基本语法",
"tags": [
"css",
"selector",
"1. 基本语法"
]
},
"css_7": {
"id": "css_7",
"source_file": "css选择器规则.txt",
"type": "explanation",
"category": "css",
"title": "2. 提取类型",
"tags": [
"css",
"selector",
"2. 提取类型"
]
},
"css_11": {
"id": "css_11",
"source_file": "css选择器规则.txt",
"type": "explanation",
"category": "css",
"title": "1. 基础选择器",
"tags": [
"css",
"selector",
"1. 基础选择器"
]
},
"css_13": {
"id": "css_13",
"source_file": "css选择器规则.txt",
"type": "explanation",
"category": "css",
"title": "2. 组合选择器",
"tags": [
"css",
"selector",
"2. 组合选择器"
]
},
"css_15": {
"id": "css_15",
"source_file": "css选择器规则.txt",
"type": "explanation",
"category": "css",
"title": "3. 伪类选择器",
"tags": [
"css",
"selector",
"3. 伪类选择器"
]
},
"css_19": {
"id": "css_19",
"source_file": "css选择器规则.txt",
"type": "explanation",
"category": "css",
"title": "1. 搜索模块",
"tags": [
"css",
"selector",
"1. 搜索模块"
]
},
"css_21": {
"id": "css_21",
"source_file": "css选择器规则.txt",
"type": "explanation",
"category": "css",
"title": "2. 详情页模块",
"tags": [
"css",
"selector",
"2. 详情页模块"
]
},
"css_23": {
"id": "css_23",
"source_file": "css选择器规则.txt",
"type": "explanation",
"category": "css",
"title": "3. 目录页模块",
"tags": [
"css",
"selector",
"3. 目录页模块"
]
},
"css_25": {
"id": "css_25",
"source_file": "css选择器规则.txt",
"type": "explanation",
"category": "css",
"title": "4. 正文页模块",
"tags": [
"css",
"selector",
"4. 正文页模块"
]
},
"css_29": {
"id": "css_29",
"source_file": "css选择器规则.txt",
"type": "explanation",
"category": "css",
"title": "1. 调试方法",
"tags": [
"css",
"selector",
"1. 调试方法"
]
},
"css_31": {
"id": "css_31",
"source_file": "css选择器规则.txt",
"type": "explanation",
"category": "css",
"title": "2. 性能优化",
"tags": [
"css",
"selector",
"2. 性能优化"
]
},
"css_35": {
"id": "css_35",
"source_file": "css选择器规则.txt",
"type": "explanation",
"category": "css",
"title": "1. 乱码问题",
"tags": [
"css",
"selector",
"1. 乱码问题"
]
},
"css_37": {
"id": "css_37",
"source_file": "css选择器规则.txt",
"type": "explanation",
"category": "css",
"title": "2. 登录验证",
"tags": [
"css",
"selector",
"2. 登录验证"
]
},
"css_39": {
"id": "css_39",
"source_file": "css选择器规则.txt",
"type": "explanation",
"category": "css",
"title": "3. 分页处理",
"tags": [
"css",
"selector",
"3. 分页处理"
]
},
"css_41": {
"id": "css_41",
"source_file": "css选择器规则.txt",
"type": "explanation",
"category": "css",
"title": "4. 动态加载",
"tags": [
"css",
"selector",
"4. 动态加载"
]
},
"css_43": {
"id": "css_43",
"source_file": "css选择器规则.txt",
"type": "explanation",
"category": "css",
"title": "5. 反爬破解",
"tags": [
"css",
"selector",
"5. 反爬破解"
]
},
"css_47": {
"id": "css_47",
"source_file": "css选择器规则.txt",
"type": "explanation",
"category": "css",
"title": "1. 核心原则",
"tags": [
"css",
"selector",
"1. 核心原则"
]
},
"css_49": {
"id": "css_49",
"source_file": "css选择器规则.txt",
"type": "explanation",
"category": "css",
"title": "2. 实用技巧",
"tags": [
"css",
"selector",
"2. 实用技巧"
]
},
"css_51": {
"id": "css_51",
"source_file": "css选择器规则.txt",
"type": "explanation",
"category": "css",
"title": "3. 调试要点",
"tags": [
"css",
"selector",
"3. 调试要点"
]
},
"css_53": {
"id": "css_53",
"source_file": "css选择器规则.txt",
"type": "explanation",
"category": "css",
"title": "关键问题及回答",
"tags": [
"css",
"selector",
"关键问题及回答"
]
},
"ref_3a.json参考.txt_357": {
"id": "ref_3a.json参考.txt_357",
"source_file": "3a.json参考.txt",
"type": "example",
"category": "configuration",
"title": "3a.json参考.txt 配置示例",
"tags": [
"reference",
"example",
"3a.json参考.txt"
]
},
"ref_Legado书源驯兽师加载器.txt_358": {
"id": "ref_Legado书源驯兽师加载器.txt_358",
"source_file": "Legado书源驯兽师加载器.txt",
"type": "example",
"category": "configuration",
"title": "Legado书源驯兽师加载器.txt 配置示例",
"tags": [
"reference",
"example",
"Legado书源驯兽师加载器.txt"
]
},
"ref_Legado书源驯兽师加载器.txt_359": {
"id": "ref_Legado书源驯兽师加载器.txt_359",
"source_file": "Legado书源驯兽师加载器.txt",
"type": "example",
"category": "configuration",
"title": "Legado书源驯兽师加载器.txt 配置示例",
"tags": [
"reference",
"example",
"Legado书源驯兽师加载器.txt"
]
},
"ref_Legado书源驯兽师加载器.txt_360": {
"id": "ref_Legado书源驯兽师加载器.txt_360",
"source_file": "Legado书源驯兽师加载器.txt",
"type": "example",
"category": "configuration",
"title": "Legado书源驯兽师加载器.txt 配置示例",
"tags": [
"reference",
"example",
"Legado书源驯兽师加载器.txt"
]
},
"ref_Legado书源驯兽师加载器.txt_361": {
"id": "ref_Legado书源驯兽师加载器.txt_361",
"source_file": "Legado书源驯兽师加载器.txt",
"type": "example",
"category": "configuration",
"title": "Legado书源驯兽师加载器.txt 配置示例",
"tags": [
"reference",
"example",
"Legado书源驯兽师加载器.txt"
]
},
"ref_TapManga.json参考.txt_362": {
"id": "ref_TapManga.json参考.txt_362",
"source_file": "TapManga.json参考.txt",
"type": "example",
"category": "configuration",
"title": "TapManga.json参考.txt 配置示例",
"tags": [
"reference",
"example",
"TapManga.json参考.txt"
]
},
"ref_TapManga.json参考.txt_363": {
"id": "ref_TapManga.json参考.txt_363",
"source_file": "TapManga.json参考.txt",
"type": "example",
"category": "configuration",
"title": "TapManga.json参考.txt 配置示例",
"tags": [
"reference",
"example",
"TapManga.json参考.txt"
]
},
"ref_喜漫漫画.json参考.txt_364": {
"id": "ref_喜漫漫画.json参考.txt_364",
"source_file": "喜漫漫画.json参考.txt",
"type": "example",
"category": "configuration",
"title": "喜漫漫画.json参考.txt 配置示例",
"tags": [
"reference",
"example",
"喜漫漫画.json参考.txt"
]
},
"ref_喜漫漫画.json参考.txt_365": {
"id": "ref_喜漫漫画.json参考.txt_365",
"source_file": "喜漫漫画.json参考.txt",
"type": "example",
"category": "configuration",
"title": "喜漫漫画.json参考.txt 配置示例",
"tags": [
"reference",
"example",
"喜漫漫画.json参考.txt"
]
},
"ref_霹雳书屋.json参考.txt_366": {
"id": "ref_霹雳书屋.json参考.txt_366",
"source_file": "霹雳书屋.json参考.txt",
"type": "example",
"category": "configuration",
"title": "霹雳书屋.json参考.txt 配置示例",
"tags": [
"reference",
"example",
"霹雳书屋.json参考.txt"
]
},
"tech_方法-JS扩展类.md_1": {
"id": "tech_方法-JS扩展类.md_1",
"source_file": "方法-JS扩展类.md",
"type": "technique",
"category": "technique",
"title": "网络请求相关",
"tags": [
"technique",
"方法-JS扩展类.md",
"网络请求相关"
]
},
"tech_方法-JS扩展类.md_3": {
"id": "tech_方法-JS扩展类.md_3",
"source_file": "方法-JS扩展类.md",
"type": "technique",
"category": "technique",
"title": "文件操作相关",
"tags": [
"technique",
"方法-JS扩展类.md",
"文件操作相关"
]
},
"tech_方法-JS扩展类.md_5": {
"id": "tech_方法-JS扩展类.md_5",
"source_file": "方法-JS扩展类.md",
"type": "technique",
"category": "technique",
"title": "编码解码相关",
"tags": [
"technique",
"方法-JS扩展类.md",
"编码解码相关"
]
},
"tech_方法-JS扩展类.md_7": {
"id": "tech_方法-JS扩展类.md_7",
"source_file": "方法-JS扩展类.md",
"type": "technique",
"category": "technique",
"title": "字符串处理相关",
"tags": [
"technique",
"方法-JS扩展类.md",
"字符串处理相关"
]
},
"tech_方法-JS扩展类.md_9": {
"id": "tech_方法-JS扩展类.md_9",
"source_file": "方法-JS扩展类.md",
"type": "technique",
"category": "technique",
"title": "字体处理相关",
"tags": [
"technique",
"方法-JS扩展类.md",
"字体处理相关"
]
},
"tech_方法-JS扩展类.md_11": {
"id": "tech_方法-JS扩展类.md_11",
"source_file": "方法-JS扩展类.md",
"type": "technique",
"category": "technique",
"title": "其他",
"tags": [
"technique",
"方法-JS扩展类.md",
"其他"
]
},
"tech_方法-加密解密.md_1": {
"id": "tech_方法-加密解密.md_1",
"source_file": "方法-加密解密.md",
"type": "technique",
"category": "technique",
"title": "消息摘要/散列消息鉴别码",
"tags": [
"technique",
"方法-加密解密.md",
"消息摘要/散列消息鉴别码"
]
},
"tech_方法-加密解密.md_3": {
"id": "tech_方法-加密解密.md_3",
"source_file": "方法-加密解密.md",
"type": "technique",
"category": "technique",
"title": "对称加密解密",
"tags": [
"technique",
"方法-加密解密.md",
"对称加密解密"
]
},
"tech_方法-加密解密.md_5": {
"id": "tech_方法-加密解密.md_5",
"source_file": "方法-加密解密.md",
"type": "technique",
"category": "technique",
"title": "非对称加密解密",
"tags": [
"technique",
"方法-加密解密.md",
"非对称加密解密"
]
},
"tech_方法-加密解密.md_7": {
"id": "tech_方法-加密解密.md_7",
"source_file": "方法-加密解密.md",
"type": "technique",
"category": "technique",
"title": "签名",
"tags": [
"technique",
"方法-加密解密.md",
"签名"
]
},
"tech_方法-加密解密.md_9": {
"id": "tech_方法-加密解密.md_9",
"source_file": "方法-加密解密.md",
"type": "technique",
"category": "technique",
"title": "MD5",
"tags": [
"technique",
"方法-加密解密.md",
"MD5"
]
},
"tech_方法-加密解密.md_11": {
"id": "tech_方法-加密解密.md_11",
"source_file": "方法-加密解密.md",
"type": "technique",
"category": "technique",
"title": "已废弃的对称加密解密方法",
"tags": [
"technique",
"方法-加密解密.md",
"已废弃的对称加密解密方法"
]
},
"tech_方法-加密解密.md_13": {
"id": "tech_方法-加密解密.md_13",
"source_file": "方法-加密解密.md",
"type": "technique",
"category": "technique",
"title": "AES 解码",
"tags": [
"technique",
"方法-加密解密.md",
"AES 解码"
]
},
"tech_方法-加密解密.md_15": {
"id": "tech_方法-加密解密.md_15",
"source_file": "方法-加密解密.md",
"type": "technique",
"category": "technique",
"title": "AES 加密",
"tags": [
"technique",
"方法-加密解密.md",
"AES 加密"
]
},
"tech_方法-加密解密.md_17": {
"id": "tech_方法-加密解密.md_17",
"source_file": "方法-加密解密.md",
"type": "technique",
"category": "technique",
"title": "DES 解码",
"tags": [
"technique",
"方法-加密解密.md",
"DES 解码"
]
},
"tech_方法-加密解密.md_19": {
"id": "tech_方法-加密解密.md_19",
"source_file": "方法-加密解密.md",
"type": "technique",
"category": "technique",
"title": "DES 加密",
"tags": [
"technique",
"方法-加密解密.md",
"DES 加密"
]
},
"tech_方法-加密解密.md_21": {
"id": "tech_方法-加密解密.md_21",
"source_file": "方法-加密解密.md",
"type": "technique",
"category": "technique",
"title": "3DES 解码",
"tags": [
"technique",
"方法-加密解密.md",
"3DES 解码"
]
},
"tech_方法-加密解密.md_23": {
"id": "tech_方法-加密解密.md_23",
"source_file": "方法-加密解密.md",
"type": "technique",
"category": "technique",
"title": "3DES 加密",
"tags": [
"technique",
"方法-加密解密.md",
"3DES 加密"
]
},
"tech_方法-登录检查JS.md_1": {
"id": "tech_方法-登录检查JS.md_1",
"source_file": "方法-登录检查JS.md",
"type": "technique",
"category": "technique",
"title": "AnalyzeUrl 构造函数",
"tags": [
"technique",
"方法-登录检查JS.md",
"AnalyzeUrl 构造函数"
]
},
"tech_方法-登录检查JS.md_3": {
"id": "tech_方法-登录检查JS.md_3",
"source_file": "方法-登录检查JS.md",
"type": "technique",
"category": "technique",
"title": "主要方法",
"tags": [
"technique",
"方法-登录检查JS.md",
"主要方法"
]
},
"tech_方法-登录检查JS.md_7": {
"id": "tech_方法-登录检查JS.md_7",
"source_file": "方法-登录检查JS.md",
"type": "technique",
"category": "technique",
"title": "UrlOption",
"tags": [
"technique",
"方法-登录检查JS.md",
"UrlOption"
]
},
"tech_方法-登录检查JS.md_9": {
"id": "tech_方法-登录检查JS.md_9",
"source_file": "方法-登录检查JS.md",
"type": "technique",
"category": "technique",
"title": "ConcurrentRecord",
"tags": [
"technique",
"方法-登录检查JS.md",
"ConcurrentRecord"
]
},
"tech_方法-登录检查JS.md_13": {
"id": "tech_方法-登录检查JS.md_13",
"source_file": "方法-登录检查JS.md",
"type": "technique",
"category": "technique",
"title": "创建`AnalyzeUrl`对象",
"tags": [
"technique",
"方法-登录检查JS.md",
"创建`AnalyzeUrl`对象"
]
},
"tech_方法-登录检查JS.md_15": {
"id": "tech_方法-登录检查JS.md_15",
"source_file": "方法-登录检查JS.md",
"type": "technique",
"category": "technique",
"title": "执行网络请求并获取响应",
"tags": [
"technique",
"方法-登录检查JS.md",
"执行网络请求并获取响应"
]
},
"tech_方法-登录检查JS.md_17": {
"id": "tech_方法-登录检查JS.md_17",
"source_file": "方法-登录检查JS.md",
"type": "technique",
"category": "technique",
"title": "上传文件",
"tags": [
"technique",
"方法-登录检查JS.md",
"上传文件"
]
},
"tech_方法-登录检查JS.md_19": {
"id": "tech_方法-登录检查JS.md_19",
"source_file": "方法-登录检查JS.md",
"type": "technique",
"category": "technique",
"title": "注意事项",
"tags": [
"technique",
"方法-登录检查JS.md",
"注意事项"
]
},
"tech_阅读js ai提示词文档基本通用(注:我使用的版本为lcy的).txt_1": {
"id": "tech_阅读js ai提示词文档基本通用(注:我使用的版本为lcy的).txt_1",
"source_file": "阅读js ai提示词文档基本通用(注:我使用的版本为lcy的).txt",
"type": "technique",
"category": "technique",
"title": "js变量和函数",
"tags": [
"technique",
"阅读js ai提示词文档基本通用(注:我使用的版本为lcy的).txt",
"js变量和函数"
]
},
"tech_阅读js ai提示词文档基本通用(注:我使用的版本为lcy的).txt_5": {
"id": "tech_阅读js ai提示词文档基本通用(注:我使用的版本为lcy的).txt_5",
"source_file": "阅读js ai提示词文档基本通用(注:我使用的版本为lcy的).txt",
"type": "technique",
"category": "technique",
"title": "[RssJsExtensions](https://github.com/Luoyacheng/legado/blob/main/app/src/main/java/io/legado/app/ui/rss/read/RssJsExtensions.kt)独有函数",
"tags": [
"technique",
"阅读js ai提示词文档基本通用(注:我使用的版本为lcy的).txt",
"[RssJsExtensions](https://github.com/Luoyacheng/legado/blob/main/app/src/main/java/io/legado/app/ui/rss/read/RssJsExtensions.kt)独有函数"
]
},
"tech_阅读js ai提示词文档基本通用(注:我使用的版本为lcy的).txt_7": {
"id": "tech_阅读js ai提示词文档基本通用(注:我使用的版本为lcy的).txt_7",
"source_file": "阅读js ai提示词文档基本通用(注:我使用的版本为lcy的).txt",
"type": "technique",
"category": "technique",
"title": "[SourceLoginJsExtensions](https://github.com/Luoyacheng/legado/blob/main/app/src/main/java/io/legado/app/ui/login/SourceLoginJsExtensions.kt)独有函数",
"tags": [
"technique",
"阅读js ai提示词文档基本通用(注:我使用的版本为lcy的).txt",
"[SourceLoginJsExtensions](https://github.com/Luoyacheng/legado/blob/main/app/src/main/java/io/legado/app/ui/login/SourceLoginJsExtensions.kt)独有函数"
]
},
"tech_阅读js ai提示词文档基本通用(注:我使用的版本为lcy的).txt_9": {
"id": "tech_阅读js ai提示词文档基本通用(注:我使用的版本为lcy的).txt_9",
"source_file": "阅读js ai提示词文档基本通用(注:我使用的版本为lcy的).txt",
"type": "technique",
"category": "technique",
"title": "[AnalyzeUrl](https://github.com/gedoor/legado/blob/master/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeUrl.kt) 部分函数",
"tags": [
"technique",
"阅读js ai提示词文档基本通用(注:我使用的版本为lcy的).txt",
"[AnalyzeUrl](https://github.com/gedoor/legado/blob/master/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeUrl.kt) 部分函数"
]
},
"tech_阅读js ai提示词文档基本通用(注:我使用的版本为lcy的).txt_11": {
"id": "tech_阅读js ai提示词文档基本通用(注:我使用的版本为lcy的).txt_11",
"source_file": "阅读js ai提示词文档基本通用(注:我使用的版本为lcy的).txt",
"type": "technique",
"category": "technique",
"title": "[AnalyzeRule](https://github.com/gedoor/legado/blob/master/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeRule.kt) 部分函数",
"tags": [
"technique",
"阅读js ai提示词文档基本通用(注:我使用的版本为lcy的).txt",
"[AnalyzeRule](https://github.com/gedoor/legado/blob/master/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeRule.kt) 部分函数"
]
},
"tech_阅读js ai提示词文档基本通用(注:我使用的版本为lcy的).txt_13": {
"id": "tech_阅读js ai提示词文档基本通用(注:我使用的版本为lcy的).txt_13",
"source_file": "阅读js ai提示词文档基本通用(注:我使用的版本为lcy的).txt",
"type": "technique",
"category": "technique",
"title": "[js扩展类](https://github.com/gedoor/legado/blob/master/app/src/main/java/io/legado/app/help/JsExtensions.kt) 部分函数",
"tags": [
"technique",
"阅读js ai提示词文档基本通用(注:我使用的版本为lcy的).txt",
"[js扩展类](https://github.com/gedoor/legado/blob/master/app/src/main/java/io/legado/app/help/JsExtensions.kt) 部分函数"
]
},
"tech_阅读js ai提示词文档基本通用(注:我使用的版本为lcy的).txt_15": {
"id": "tech_阅读js ai提示词文档基本通用(注:我使用的版本为lcy的).txt_15",
"source_file": "阅读js ai提示词文档基本通用(注:我使用的版本为lcy的).txt",
"type": "technique",
"category": "technique",
"title": "[js加解密类](https://github.com/gedoor/legado/blob/master/app/src/main/java/io/legado/app/help/JsEncodeUtils.kt) 部分函数",
"tags": [
"technique",
"阅读js ai提示词文档基本通用(注:我使用的版本为lcy的).txt",
"[js加解密类](https://github.com/gedoor/legado/blob/master/app/src/main/java/io/legado/app/help/JsEncodeUtils.kt) 部分函数"
]
},
"tech_阅读js ai提示词文档基本通用(注:我使用的版本为lcy的).txt_19": {
"id": "tech_阅读js ai提示词文档基本通用(注:我使用的版本为lcy的).txt_19",
"source_file": "阅读js ai提示词文档基本通用(注:我使用的版本为lcy的).txt",
"type": "technique",
"category": "technique",
"title": "属性",
"tags": [
"technique",
"阅读js ai提示词文档基本通用(注:我使用的版本为lcy的).txt",
"属性"
]
},
"tech_阅读js ai提示词文档基本通用(注:我使用的版本为lcy的).txt_21": {
"id": "tech_阅读js ai提示词文档基本通用(注:我使用的版本为lcy的).txt_21",
"source_file": "阅读js ai提示词文档基本通用(注:我使用的版本为lcy的).txt",
"type": "technique",
"category": "technique",
"title": "book对象的部分可用函数",
"tags": [
"technique",
"阅读js ai提示词文档基本通用(注:我使用的版本为lcy的).txt",
"book对象的部分可用函数"
]
},
"tech_阅读js ai提示词文档基本通用(注:我使用的版本为lcy的).txt_23": {
"id": "tech_阅读js ai提示词文档基本通用(注:我使用的版本为lcy的).txt_23",
"source_file": "阅读js ai提示词文档基本通用(注:我使用的版本为lcy的).txt",
"type": "technique",
"category": "technique",
"title": "chapter对象的部分可用属性",
"tags": [
"technique",
"阅读js ai提示词文档基本通用(注:我使用的版本为lcy的).txt",
"chapter对象的部分可用属性"
]
},
"tech_阅读js ai提示词文档基本通用(注:我使用的版本为lcy的).txt_25": {
"id": "tech_阅读js ai提示词文档基本通用(注:我使用的版本为lcy的).txt_25",
"source_file": "阅读js ai提示词文档基本通用(注:我使用的版本为lcy的).txt",
"type": "technique",
"category": "technique",
"title": "source对象的部分可用函数",
"tags": [
"technique",
"阅读js ai提示词文档基本通用(注:我使用的版本为lcy的).txt",
"source对象的部分可用函数"
]
},
"tech_阅读js ai提示词文档基本通用(注:我使用的版本为lcy的).txt_27": {
"id": "tech_阅读js ai提示词文档基本通用(注:我使用的版本为lcy的).txt_27",
"source_file": "阅读js ai提示词文档基本通用(注:我使用的版本为lcy的).txt",
"type": "technique",
"category": "technique",
"title": "cookie对象的部分可用函数",
"tags": [
"technique",
"阅读js ai提示词文档基本通用(注:我使用的版本为lcy的).txt",
"cookie对象的部分可用函数"
]
},
"tech_阅读js ai提示词文档基本通用(注:我使用的版本为lcy的).txt_29": {
"id": "tech_阅读js ai提示词文档基本通用(注:我使用的版本为lcy的).txt_29",
"source_file": "阅读js ai提示词文档基本通用(注:我使用的版本为lcy的).txt",
"type": "technique",
"category": "technique",
"title": "cache对象的部分可用函数",
"tags": [
"technique",
"阅读js ai提示词文档基本通用(注:我使用的版本为lcy的).txt",
"cache对象的部分可用函数"
]
},
"tech_阅读js ai提示词文档基本通用(注:我使用的版本为lcy的).txt_31": {
"id": "tech_阅读js ai提示词文档基本通用(注:我使用的版本为lcy的).txt_31",
"source_file": "阅读js ai提示词文档基本通用(注:我使用的版本为lcy的).txt",
"type": "technique",
"category": "technique",
"title": "跳转外部链接/应用函数",
"tags": [
"technique",
"阅读js ai提示词文档基本通用(注:我使用的版本为lcy的).txt",
"跳转外部链接/应用函数"
]
},
"tech_阅读js ai提示词文档基本通用(注:我使用的版本为lcy的).txt_33": {
"id": "tech_阅读js ai提示词文档基本通用(注:我使用的版本为lcy的).txt_33",
"source_file": "阅读js ai提示词文档基本通用(注:我使用的版本为lcy的).txt",
"type": "technique",
"category": "technique",
"title": "视频播放器函数",
"tags": [
"technique",
"阅读js ai提示词文档基本通用(注:我使用的版本为lcy的).txt",
"视频播放器函数"
]
}
},
"book_sources": {
"1751_🏷晋江文学_书源_20260218_102134.md": {
"source_name": "1751_🏷晋江文学_书源_20260218_102134",
"source_url": "",
"tags": [
"1751"
]
},
"1751_🏷晋江文学_书源_20260218_103141.md": {
"source_name": "1751_🏷晋江文学_书源_20260218_103141",
"source_url": "",
"tags": [
"1751"
]
},
"4925_📚豆瓣阅读_书源_20260218_103229.md": {
"source_name": "4925_📚豆瓣阅读_书源_20260218_103229",
"source_url": "",
"tags": [
"4925"
]
},
"5718_书耽_书源_20260218_103302.md": {
"source_name": "5718_书耽_书源_20260218_103302",
"source_url": "",
"tags": [
"5718"
]
},
"6077_同人小说网_书源_20260218_102141.md": {
"source_name": "6077_同人小说网_书源_20260218_102141",
"source_url": "",
"tags": [
"6077"
]
},
"6077_同人小说网_书源_20260218_103149.md": {
"source_name": "6077_同人小说网_书源_20260218_103149",
"source_url": "",
"tags": [
"6077"
]
},
"6112_🌈网易云音乐_书源_20260218_103251.md": {
"source_name": "6112_🌈网易云音乐_书源_20260218_103251",
"source_url": "",
"tags": [
"6112"
]
},
"6332_🔞完本小说网_书源_20260218_102153.md": {
"source_name": "6332_🔞完本小说网_书源_20260218_102153",
"source_url": "",
"tags": [
"6332"
]
},
"6332_🔞完本小说网_书源_20260218_103159.md": {
"source_name": "6332_🔞完本小说网_书源_20260218_103159",
"source_url": "",
"tags": [
"6332"
]
},
"6746_🌞A晴天聚合5.2.03(终极版)_书源_20260218_102156.md": {
"source_name": "6746_🌞A晴天聚合5.2.03(终极版)_书源_20260218_102156",
"source_url": "",
"tags": [
"6746"
]
},
"6746_🌞A晴天聚合5.2.03(终极版)_书源_20260218_103201.md": {
"source_name": "6746_🌞A晴天聚合5.2.03(终极版)_书源_20260218_103201",
"source_url": "",
"tags": [
"6746"
]
},
"6752_旧钢笔_书源_20260218_103342.md": {
"source_name": "6752_旧钢笔_书源_20260218_103342",
"source_url": "",
"tags": [
"6752"
]
},
"6753_kuronavi漫画raw_书源_20260218_103340.md": {
"source_name": "6753_kuronavi漫画raw_书源_20260218_103340",
"source_url": "",
"tags": [
"6753"
]
},
"6754_云轩阁小说网_书源_20260218_103339.md": {
"source_name": "6754_云轩阁小说网_书源_20260218_103339",
"source_url": "",
"tags": [
"6754"
]
},
"6758_亲亲小说qinqinxiaoshuo_书源_20260218_103337.md": {
"source_name": "6758_亲亲小说qinqinxiaoshuo_书源_20260218_103337",
"source_url": "",
"tags": [
"6758"
]
},
"6759_🄼 八一_书源_20260218_103335.md": {
"source_name": "6759_🄼 八一_书源_20260218_103335",
"source_url": "",
"tags": [
"6759"
]
},
"6760_ꂵ 30读书_书源_20260218_103334.md": {
"source_name": "6760_ꂵ 30读书_书源_20260218_103334",
"source_url": "",
"tags": [
"6760"
]
},
"6761_番茄(发现)_书源_20260218_103333.md": {
"source_name": "6761_番茄(发现)_书源_20260218_103333",
"source_url": "",
"tags": [
"6761"
]
},
"6763_九桃小说_书源_20260218_103332.md": {
"source_name": "6763_九桃小说_书源_20260218_103332",
"source_url": "",
"tags": [
"6763"
]
},
"6771_600小说网_书源_20260218_103331.md": {
"source_name": "6771_600小说网_书源_20260218_103331",
"source_url": "",
"tags": [
"6771"
]
},
"6772_顶点小说_书源_20260218_103329.md": {
"source_name": "6772_顶点小说_书源_20260218_103329",
"source_url": "",
"tags": [
"6772"
]
},
"6773_🄼冰清阁小说_书源_20260218_103328.md": {
"source_name": "6773_🄼冰清阁小说_书源_20260218_103328",
"source_url": "",
"tags": [
"6773"
]
},
"6774_小说520_书源_20260218_103327.md": {
"source_name": "6774_小说520_书源_20260218_103327",
"source_url": "",
"tags": [
"6774"
]
},
"6775_365小说网_书源_20260218_103326.md": {
"source_name": "6775_365小说网_书源_20260218_103326",
"source_url": "",
"tags": [
"6775"
]
},
"6776_全能书源_书源_20260218_103324.md": {
"source_name": "6776_全能书源_书源_20260218_103324",
"source_url": "",
"tags": [
"6776"
]
},
"6777_🌈笔趣中文网_书源_20260218_103323.md": {
"source_name": "6777_🌈笔趣中文网_书源_20260218_103323",
"source_url": "",
"tags": [
"6777"
]
},
"6778_🎨禁漫天堂_书源_20260218_103322.md": {
"source_name": "6778_🎨禁漫天堂_书源_20260218_103322",
"source_url": "",
"tags": [
"6778"
]
},
"6779_ESJ Zone_书源_20260218_103321.md": {
"source_name": "6779_ESJ Zone_书源_20260218_103321",
"source_url": "",
"tags": [
"6779"
]
},
"6781_看看影院_书源_20260218_103319.md": {
"source_name": "6781_看看影院_书源_20260218_103319",
"source_url": "",
"tags": [
"6781"
]
},
"6782_夜读集小说_书源_20260218_103318.md": {
"source_name": "6782_夜读集小说_书源_20260218_103318",
"source_url": "",
"tags": [
"6782"
]
},
"6783_爱下电子书_书源_20260218_103317.md": {
"source_name": "6783_爱下电子书_书源_20260218_103317",
"source_url": "",
"tags": [
"6783"
]
},
"6785_必去读书库_书源_20260218_103316.md": {
"source_name": "6785_必去读书库_书源_20260218_103316",
"source_url": "",
"tags": [
"6785"
]
},
"6792_天天看小说🪜_书源_20260218_103315.md": {
"source_name": "6792_天天看小说🪜_书源_20260218_103315",
"source_url": "",
"tags": [
"6792"
]
},
"6803_百草露小说_书源_20260218_103313.md": {
"source_name": "6803_百草露小说_书源_20260218_103313",
"source_url": "",
"tags": [
"6803"
]
},
"6806_🌸绅士漫画_书源_20260218_103312.md": {
"source_name": "6806_🌸绅士漫画_书源_20260218_103312",
"source_url": "",
"tags": [
"6806"
]
},
"6807_哔哩哔哩_书源_20260218_102201.md": {
"source_name": "6807_哔哩哔哩_书源_20260218_102201",
"source_url": "",
"tags": [
"6807"
]
},
"6807_哔哩哔哩_书源_20260218_103202.md": {
"source_name": "6807_哔哩哔哩_书源_20260218_103202",
"source_url": "",
"tags": [
"6807"
]
},
"6808_笔趣阁_书源_20260218_103311.md": {
"source_name": "6808_笔趣阁_书源_20260218_103311",
"source_url": "",
"tags": [
"6808"
]
},
"6809_八叉书库_书源_20260218_103310.md": {
"source_name": "6809_八叉书库_书源_20260218_103310",
"source_url": "",
"tags": [
"6809"
]
},
"6810_爱下电子书_书源_20260218_103308.md": {
"source_name": "6810_爱下电子书_书源_20260218_103308",
"source_url": "",
"tags": [
"6810"
]
},
"6812_🌸Hitomi漫_书源_20260218_103307.md": {
"source_name": "6812_🌸Hitomi漫_书源_20260218_103307",
"source_url": "",
"tags": [
"6812"
]
},
"6813_爱看书吧_书源_20260218_103306.md": {
"source_name": "6813_爱看书吧_书源_20260218_103306",
"source_url": "",
"tags": [
"6813"
]
},
"6814_八叉书库_书源_20260218_103305.md": {
"source_name": "6814_八叉书库_书源_20260218_103305",
"source_url": "",
"tags": [
"6814"
]
},
"6815_🌸肉漫屋( 韩漫+ )_书源_20260218_103303.md": {
"source_name": "6815_🌸肉漫屋( 韩漫+ )_书源_20260218_103303",
"source_url": "",
"tags": [
"6815"
]
},
"6816_🌸爱丽丝书屋(免翻)_书源_20260218_103301.md": {
"source_name": "6816_🌸爱丽丝书屋(免翻)_书源_20260218_103301",
"source_url": "",
"tags": [
"6816"
]
},
"6817_番茄四合一_书源_20260218_103300.md": {
"source_name": "6817_番茄四合一_书源_20260218_103300",
"source_url": "",
"tags": [
"6817"
]
},
"6819_CA 情色小说(修复)_书源_20260218_103259.md": {
"source_name": "6819_CA 情色小说(修复)_书源_20260218_103259",
"source_url": "",
"tags": [
"6819"
]
},
"6820_🌸肉漫屋( 韩漫+ )_书源_20260218_103257.md": {
"source_name": "6820_🌸肉漫屋( 韩漫+ )_书源_20260218_103257",
"source_url": "",
"tags": [
"6820"
]
},
"6830_万能精品源_书源_20260218_103256.md": {
"source_name": "6830_万能精品源_书源_20260218_103256",
"source_url": "",
"tags": [
"6830"
]
},
"6832_茶杯狐_书源_20260218_103255.md": {
"source_name": "6832_茶杯狐_书源_20260218_103255",
"source_url": "",
"tags": [
"6832"
]
},
"6834_天命皆烬小说_书源_20260218_103254.md": {
"source_name": "6834_天命皆烬小说_书源_20260218_103254",
"source_url": "",
"tags": [
"6834"
]
},
"6835_茶马小说 [耽美]_书源_20260218_103252.md": {
"source_name": "6835_茶马小说 [耽美]_书源_20260218_103252",
"source_url": "",
"tags": [
"6835"
]
},
"6837_美女图片网_书源_20260218_103250.md": {
"source_name": "6837_美女图片网_书源_20260218_103250",
"source_url": "",
"tags": [
"6837"
]
},
"6841_🍅大灰狼聚合5.2.2(完全版)_书源_20260218_102216.md": {
"source_name": "6841_🍅大灰狼聚合5.2.2(完全版)_书源_20260218_102216",
"source_url": "",
"tags": [
"6841"
]
},
"6841_🍅大灰狼聚合5.2.2(完全版)_书源_20260218_103213.md": {
"source_name": "6841_🍅大灰狼聚合5.2.2(完全版)_书源_20260218_103213",
"source_url": "",
"tags": [
"6841"
]
},
"6842_汉书书_书源_20260218_103249.md": {
"source_name": "6842_汉书书_书源_20260218_103249",
"source_url": "",
"tags": [
"6842"
]
},
"6844_速读谷_书源_20260218_103247.md": {
"source_name": "6844_速读谷_书源_20260218_103247",
"source_url": "",
"tags": [
"6844"
]
},
"6857_多平台精品源_书源_20260218_103246.md": {
"source_name": "6857_多平台精品源_书源_20260218_103246",
"source_url": "",
"tags": [
"6857"
]
},
"6873_📖Lofter_书源_20260218_102132.md": {
"source_name": "6873_📖Lofter_书源_20260218_102132",
"source_url": "",
"tags": [
"6873"
]
},
"6873_📖Lofter_书源_20260218_102731.md": {
"source_name": "6873_📖Lofter_书源_20260218_102731",
"source_url": "",
"tags": [
"6873"
]
},
"6873_📖Lofter_书源_20260218_103117.md": {
"source_name": "6873_📖Lofter_书源_20260218_103117",
"source_url": "",
"tags": [
"6873"
]
},
"6874_无插件 - 赛事直播_书源_20260218_103245.md": {
"source_name": "6874_无插件 - 赛事直播_书源_20260218_103245",
"source_url": "",
"tags": [
"6874"
]
},
"6875_顶点小说ddxsmf_书源_20260218_103244.md": {
"source_name": "6875_顶点小说ddxsmf_书源_20260218_103244",
"source_url": "",
"tags": [
"6875"
]
},
"6876_18r(免梯子)_书源_20260218_103243.md": {
"source_name": "6876_18r(免梯子)_书源_20260218_103243",
"source_url": "",
"tags": [
"6876"
]
},
"6880_速读谷_书源_20260218_103241.md": {
"source_name": "6880_速读谷_书源_20260218_103241",
"source_url": "",
"tags": [
"6880"
]
},
"6881_ncode_syosetu(成为小说家吧)_书源_20260218_103240.md": {
"source_name": "6881_ncode_syosetu(成为小说家吧)_书源_20260218_103240",
"source_url": "",
"tags": [
"6881"
]
},
"6882_八叉书库_书源_20260218_103239.md": {
"source_name": "6882_八叉书库_书源_20260218_103239",
"source_url": "",
"tags": [
"6882"
]
},
"6883_🎨 177漫画🔥_书源_20260218_103238.md": {
"source_name": "6883_🎨 177漫画🔥_书源_20260218_103238",
"source_url": "",
"tags": [
"6883"
]
},
"6884_🍅团夕小说_书源_20260218_103236.md": {
"source_name": "6884_🍅团夕小说_书源_20260218_103236",
"source_url": "",
"tags": [
"6884"
]
},
"6885_🔞UAA-免梯-已绕过会员_书源_20260218_103235.md": {
"source_name": "6885_🔞UAA-免梯-已绕过会员_书源_20260218_103235",
"source_url": "",
"tags": [
"6885"
]
},
"6887_🎉 八零小说_书源_20260218_103233.md": {
"source_name": "6887_🎉 八零小说_书源_20260218_103233",
"source_url": "",
"tags": [
"6887"
]
},
"6890_苦读书_书源_20260218_103232.md": {
"source_name": "6890_苦读书_书源_20260218_103232",
"source_url": "",
"tags": [
"6890"
]
},
"6892_🍅番茄专业户_书源_20260218_103228.md": {
"source_name": "6892_🍅番茄专业户_书源_20260218_103228",
"source_url": "",
"tags": [
"6892"
]
},
"6895_篱笆好_书源_20260218_102230.md": {
"source_name": "6895_篱笆好_书源_20260218_102230",
"source_url": "",
"tags": [
"6895"
]
},
"6895_篱笆好_书源_20260218_103227.md": {
"source_name": "6895_篱笆好_书源_20260218_103227",
"source_url": "",
"tags": [
"6895"
]
},
"6896_元小说_书源_20260218_102228.md": {
"source_name": "6896_元小说_书源_20260218_102228",
"source_url": "",
"tags": [
"6896"
]
},
"6896_元小说_书源_20260218_103225.md": {
"source_name": "6896_元小说_书源_20260218_103225",
"source_url": "",
"tags": [
"6896"
]
},
"6897_篱笆好文(优+++)_书源_20260218_102227.md": {
"source_name": "6897_篱笆好文(优+++)_书源_20260218_102227",
"source_url": "",
"tags": [
"6897"
]
},
"6897_篱笆好文(优+++)_书源_20260218_103224.md": {
"source_name": "6897_篱笆好文(优+++)_书源_20260218_103224",
"source_url": "",
"tags": [
"6897"
]
},
"6900_闲看免费小说_书源_20260218_102226.md": {
"source_name": "6900_闲看免费小说_书源_20260218_102226",
"source_url": "",
"tags": [
"6900"
]
},
"6900_闲看免费小说_书源_20260218_103223.md": {
"source_name": "6900_闲看免费小说_书源_20260218_103223",
"source_url": "",
"tags": [
"6900"
]
},
"6904_🍅 番茄的缘_书源_20260218_102225.md": {
"source_name": "6904_🍅 番茄的缘_书源_20260218_102225",
"source_url": "",
"tags": [
"6904"
]
},
"6904_🍅 番茄的缘_书源_20260218_103222.md": {
"source_name": "6904_🍅 番茄的缘_书源_20260218_103222",
"source_url": "",
"tags": [
"6904"
]
},
"6905_🍅番茄,七猫,塔读,得间,书旗(段评版聚合源)_书源_20260218_102143.md": {
"source_name": "6905_🍅番茄,七猫,塔读,得间,书旗(段评版聚合源)_书源_20260218_102143",
"source_url": "",
"tags": [
"6905"
]
},
"6905_🍅番茄,七猫,塔读,得间,书旗(段评版聚合源)_书源_20260218_103150.md": {
"source_name": "6905_🍅番茄,七猫,塔读,得间,书旗(段评版聚合源)_书源_20260218_103150",
"source_url": "",
"tags": [
"6905"
]
},
"6906_速读谷伪_书源_20260218_102223.md": {
"source_name": "6906_速读谷伪_书源_20260218_102223",
"source_url": "",
"tags": [
"6906"
]
},
"6906_速读谷伪_书源_20260218_103221.md": {
"source_name": "6906_速读谷伪_书源_20260218_103221",
"source_url": "",
"tags": [
"6906"
]
},
"6907_💫悦恋免费小说 - [☆星眠☆]_书源_20260218_102222.md": {
"source_name": "6907_💫悦恋免费小说 - [☆星眠☆]_书源_20260218_102222",
"source_url": "",
"tags": [
"6907"
]
},
"6907_💫悦恋免费小说 - [☆星眠☆]_书源_20260218_103219.md": {
"source_name": "6907_💫悦恋免费小说 - [☆星眠☆]_书源_20260218_103219",
"source_url": "",
"tags": [
"6907"
]
},
"6917_龙头小说_书源_20260218_102221.md": {
"source_name": "6917_龙头小说_书源_20260218_102221",
"source_url": "",
"tags": [
"6917"
]
},
"6917_龙头小说_书源_20260218_103218.md": {
"source_name": "6917_龙头小说_书源_20260218_103218",
"source_url": "",
"tags": [
"6917"
]
},
"6918_📖笔趣网_书源_20260218_102220.md": {
"source_name": "6918_📖笔趣网_书源_20260218_102220",
"source_url": "",
"tags": [
"6918"
]
},
"6918_📖笔趣网_书源_20260218_103217.md": {
"source_name": "6918_📖笔趣网_书源_20260218_103217",
"source_url": "",
"tags": [
"6918"
]
},
"6921_📚书山聚合_书源_20260218_102219.md": {
"source_name": "6921_📚书山聚合_书源_20260218_102219",
"source_url": "",
"tags": [
"6921"
]
},
"6921_📚书山聚合_书源_20260218_103216.md": {
"source_name": "6921_📚书山聚合_书源_20260218_103216",
"source_url": "",
"tags": [
"6921"
]
},
"6923_久久漫画_书源_20260218_102217.md": {
"source_name": "6923_久久漫画_书源_20260218_102217",
"source_url": "",
"tags": [
"6923"
]
},
"6923_久久漫画_书源_20260218_103214.md": {
"source_name": "6923_久久漫画_书源_20260218_103214",
"source_url": "",
"tags": [
"6923"
]
},
"6927_书豪小说网_书源_20260218_102215.md": {
"source_name": "6927_书豪小说网_书源_20260218_102215",
"source_url": "",
"tags": [
"6927"
]
},
"6927_书豪小说网_书源_20260218_103212.md": {
"source_name": "6927_书豪小说网_书源_20260218_103212",
"source_url": "",
"tags": [
"6927"
]
},
"6928_速读谷_书源_20260218_102213.md": {
"source_name": "6928_速读谷_书源_20260218_102213",
"source_url": "",
"tags": [
"6928"
]
},
"6928_速读谷_书源_20260218_103211.md": {
"source_name": "6928_速读谷_书源_20260218_103211",
"source_url": "",
"tags": [
"6928"
]
},
"6930_东方小说网_书源_20260218_102211.md": {
"source_name": "6930_东方小说网_书源_20260218_102211",
"source_url": "",
"tags": [
"6930"
]
},
"6930_东方小说网_书源_20260218_103208.md": {
"source_name": "6930_东方小说网_书源_20260218_103208",
"source_url": "",
"tags": [
"6930"
]
},
"6931_梦想家文学_书源_20260218_102210.md": {
"source_name": "6931_梦想家文学_书源_20260218_102210",
"source_url": "",
"tags": [
"6931"
]
},
"6931_梦想家文学_书源_20260218_103207.md": {
"source_name": "6931_梦想家文学_书源_20260218_103207",
"source_url": "",
"tags": [
"6931"
]
},
"6932_🍓番茄免费小说-[春节限定版]_书源_20260218_102209.md": {
"source_name": "6932_🍓番茄免费小说-[春节限定版]_书源_20260218_102209",
"source_url": "",
"tags": [
"6932"
]
},
"6932_🍓番茄免费小说-[春节限定版]_书源_20260218_103206.md": {
"source_name": "6932_🍓番茄免费小说-[春节限定版]_书源_20260218_103206",
"source_url": "",
"tags": [
"6932"
]
},
"6933_漫小肆20251217_书源_20260218_102207.md": {
"source_name": "6933_漫小肆20251217_书源_20260218_102207",
"source_url": "",
"tags": [
"6933"
]
},
"6933_漫小肆20251217_书源_20260218_103205.md": {
"source_name": "6933_漫小肆20251217_书源_20260218_103205",
"source_url": "",
"tags": [
"6933"
]
},
"6934_东方小说网_书源_20260218_102203.md": {
"source_name": "6934_东方小说网_书源_20260218_102203",
"source_url": "",
"tags": [
"6934"
]
},
"6934_东方小说网_书源_20260218_103203.md": {
"source_name": "6934_东方小说网_书源_20260218_103203",
"source_url": "",
"tags": [
"6934"
]
},
"6935_速读谷_书源_20260218_102154.md": {
"source_name": "6935_速读谷_书源_20260218_102154",
"source_url": "",
"tags": [
"6935"
]
},
"6935_速读谷_书源_20260218_103200.md": {
"source_name": "6935_速读谷_书源_20260218_103200",
"source_url": "",
"tags": [
"6935"
]
},
"6936_得奇小说网_书源_20260218_102152.md": {
"source_name": "6936_得奇小说网_书源_20260218_102152",
"source_url": "",
"tags": [
"6936"
]
},
"6936_得奇小说网_书源_20260218_103157.md": {
"source_name": "6936_得奇小说网_书源_20260218_103157",
"source_url": "",
"tags": [
"6936"
]
},
"6937_🎆起点中文网(按钮筛选)_书源_20260218_102151.md": {
"source_name": "6937_🎆起点中文网(按钮筛选)_书源_20260218_102151",
"source_url": "",
"tags": [
"6937"
]
},
"6937_🎆起点中文网(按钮筛选)_书源_20260218_103156.md": {
"source_name": "6937_🎆起点中文网(按钮筛选)_书源_20260218_103156",
"source_url": "",
"tags": [
"6937"
]
},
"6940_📪第一版主820_书源_20260218_102149.md": {
"source_name": "6940_📪第一版主820_书源_20260218_102149",
"source_url": "",
"tags": [
"6940"
]
},
"6940_📪第一版主820_书源_20260218_103155.md": {
"source_name": "6940_📪第一版主820_书源_20260218_103155",
"source_url": "",
"tags": [
"6940"
]
},
"6941_🍓番茄免费小说-[春节限定版]_书源_20260218_102148.md": {
"source_name": "6941_🍓番茄免费小说-[春节限定版]_书源_20260218_102148",
"source_url": "",
"tags": [
"6941"
]
},
"6941_🍓番茄免费小说-[春节限定版]_书源_20260218_103154.md": {
"source_name": "6941_🍓番茄免费小说-[春节限定版]_书源_20260218_103154",
"source_url": "",
"tags": [
"6941"
]
},
"6942_万能精品聚合_书源_20260218_102146.md": {
"source_name": "6942_万能精品聚合_书源_20260218_102146",
"source_url": "",
"tags": [
"6942"
]
},
"6942_万能精品聚合_书源_20260218_103152.md": {
"source_name": "6942_万能精品聚合_书源_20260218_103152",
"source_url": "",
"tags": [
"6942"
]
},
"6943_📪第一版主820_书源_20260218_102144.md": {
"source_name": "6943_📪第一版主820_书源_20260218_102144",
"source_url": "",
"tags": [
"6943"
]
},
"6943_📪第一版主820_书源_20260218_103151.md": {
"source_name": "6943_📪第一版主820_书源_20260218_103151",
"source_url": "",
"tags": [
"6943"
]
},
"6946_📪第一版主_书源_20260218_102140.md": {
"source_name": "6946_📪第一版主_书源_20260218_102140",
"source_url": "",
"tags": [
"6946"
]
},
"6946_📪第一版主_书源_20260218_103147.md": {
"source_name": "6946_📪第一版主_书源_20260218_103147",
"source_url": "",
"tags": [
"6946"
]
},
"6948_乐乐_书源_20260218_102138.md": {
"source_name": "6948_乐乐_书源_20260218_102138",
"source_url": "",
"tags": [
"6948"
]
},
"6948_乐乐_书源_20260218_103145.md": {
"source_name": "6948_乐乐_书源_20260218_103145",
"source_url": "",
"tags": [
"6948"
]
},
"6949_🔞乐成漫画_书源_20260218_102136.md": {
"source_name": "6949_🔞乐成漫画_书源_20260218_102136",
"source_url": "",
"tags": [
"6949"
]
},
"6949_🔞乐成漫画_书源_20260218_103144.md": {
"source_name": "6949_🔞乐成漫画_书源_20260218_103144",
"source_url": "",
"tags": [
"6949"
]
},
"6965_📖笔趣阁手机版_书源_20260218_102135.md": {
"source_name": "6965_📖笔趣阁手机版_书源_20260218_102135",
"source_url": "",
"tags": [
"6965"
]
},
"6965_📖笔趣阁手机版_书源_20260218_103143.md": {
"source_name": "6965_📖笔趣阁手机版_书源_20260218_103143",
"source_url": "",
"tags": [
"6965"
]
},
"unknown_无插件 - 赛事直播_书源_20260218_103125.md": {
"source_name": "unknown_无插件 - 赛事直播_书源_20260218_103125",
"source_url": "",
"tags": [
"unknown"
]
}
},
"patterns": [],
"selectors": [
".book-name",
".author",
".cover",
".content",
".div",
".-1",
"#content",
"#main",
".container",
".item",
".book-item",
".title",
".author-info",
".intro",
".cover-img",
".chapter-list",
".next-page",
".log",
".toString",
".get",
".put"
]
}
FILE:references/knowledge_base/learning_stats/learning_stats.json
{
"learning_stats": {
"total_files": 150,
"learned_entries": 408,
"book_sources": 134,
"patterns": 0,
"selectors": 28
},
"knowledge_count": 408,
"book_source_count": 134,
"pattern_count": 0,
"selector_count": 21
}
FILE:references/knowledge_base/learning_stats/stats.json
{
"书源": {
"total_learned": 134,
"last_update": "2026-02-18T10:33:42.051905"
},
"last_global_update": "2026-02-18T10:33:42.051917"
}
FILE:references/legado_data_structures.md
# Legado Data Structures & Source Code Analysis
Based on analysis of 397,380 lines of Legado source code.
## Table of Contents
1. [BookSource Data Structure](#booksource)
2. [Rule Data Structures](#rules)
3. [Rule Parsing Engine](#engine)
4. [Database Schema](#database)
5. [Required Fields Summary](#required)
---
## BookSource Data Structure (BookSource.kt)
```kotlin
data class BookSource(
@PrimaryKey var bookSourceUrl: String = "", // Required
var bookSourceName: String = "", // Required
var bookSourceGroup: String? = null, // 分组
var bookSourceType: Int = 0, // 0=文本 1=音频 2=图片 3=文件 4=视频
var bookUrlPattern: String? = null,
var customOrder: Int = 0,
var enabled: Boolean = true,
var enabledExplore: Boolean = true,
var jsLib: String? = null,
var enabledCookieJar: Boolean? = true,
var concurrentRate: String? = null,
var header: String? = null,
var loginUrl: String? = null,
var loginUi: String? = null,
var loginCheckJs: String? = null,
var coverDecodeJs: String? = null,
var bookSourceComment: String? = null,
var variableComment: String? = null,
var lastUpdateTime: Long = 0,
var respondTime: Long = 180000L,
var weight: Int = 0,
var exploreUrl: String? = null,
var exploreScreen: String? = null,
var ruleExplore: ExploreRule? = null,
var searchUrl: String? = null,
var ruleSearch: SearchRule? = null,
var ruleBookInfo: BookInfoRule? = null,
var ruleToc: TocRule? = null,
var ruleContent: ContentRule? = null,
var ruleReview: ReviewRule? = null,
var eventListener: Boolean = false,
var customButton: Boolean = false
)
```
---
## Rule Data Structures
### SearchRule
```kotlin
data class SearchRule(
var checkKeyWord: String? = null, // 校验关键字
var bookList: String? = null, // Required
var name: String? = null, // Required
var author: String? = null,
var intro: String? = null,
var kind: String? = null,
var lastChapter: String? = null,
var updateTime: String? = null,
var bookUrl: String? = null, // Required
var coverUrl: String? = null,
var wordCount: String? = null
)
```
### BookInfoRule
```kotlin
data class BookInfoRule(
var init: String? = null,
var name: String? = null, // Required
var author: String? = null,
var intro: String? = null,
var kind: String? = null,
var lastChapter: String? = null,
var updateTime: String? = null,
var coverUrl: String? = null,
var tocUrl: String? = null,
var wordCount: String? = null,
var canReName: String? = null,
var downloadUrls: String? = null
)
```
### TocRule
```kotlin
data class TocRule(
var preUpdateJs: String? = null,
var chapterList: String? = null, // Required
var chapterName: String? = null, // Required
var chapterUrl: String? = null, // Required
var formatJs: String? = null,
var isVolume: String? = null,
var isVip: String? = null,
var isPay: String? = null,
var updateTime: String? = null,
var nextTocUrl: String? = null
)
```
### ContentRule
```kotlin
data class ContentRule(
var content: String? = null, // Required
var subContent: String? = null, // 副文规则
var title: String? = null, // 有些网站只能在正文中获取标题
var nextContentUrl: String? = null, // 下一章链接 (NOT prevContentUrl!)
var webJs: String? = null,
var sourceRegex: String? = null,
var replaceRegex: String? = null,
var imageStyle: String? = null,
var imageDecode: String? = null,
var payAction: String? = null,
var callBackJs: String? = null
)
```
---
## Rule Parsing Engine (AnalyzeRule.kt)
**Parsing Modes:**
| Mode | Engine | Use Case |
|------|--------|----------|
| Default | JSoup CSS | Standard HTML parsing |
| Json | JSONPath | JSON data sources |
| XPath | XPath | XML/HTML documents |
| Js | JavaScript | Script evaluation |
| WebJs | WebView JS | Dynamic/SPA content |
**Rule String Format:** `CSS选择器@提取类型##正则表达式##替换内容`
**Key Methods:**
```kotlin
fun getString(ruleStr: String?, mContent: Any?, isUrl: Boolean): String
fun getStringList(ruleStr: String?, mContent: Any?, isUrl: Boolean): List<String>?
fun setContent(content: Any?, baseUrl: String?): AnalyzeRule
```
**Processing Flow:**
1. Parse rule string into `SourceRule` list
2. Apply each rule sequentially
3. Handle mode switching (CSS/JSON/XPath/JS)
4. Apply regex replacements
5. Return final result
---
## Database Schema (Room)
```sql
CREATE TABLE bookSources (
bookSourceName TEXT NOT NULL,
bookSourceGroup TEXT,
bookSourceUrl TEXT NOT NULL PRIMARY KEY,
bookSourceType INTEGER NOT NULL,
bookUrlPattern TEXT,
customOrder INTEGER NOT NULL DEFAULT 0,
enabled INTEGER NOT NULL DEFAULT 1,
enabledExplore INTEGER NOT NULL DEFAULT 1,
jsLib TEXT,
enabledCookieJar INTEGER DEFAULT 0,
header TEXT,
loginUrl TEXT,
loginUi TEXT,
coverDecodeJs TEXT,
bookSourceComment TEXT,
variableComment TEXT,
lastUpdateTime INTEGER NOT NULL,
respondTime INTEGER NOT NULL DEFAULT 180000,
weight INTEGER NOT NULL DEFAULT 0,
exploreUrl TEXT,
exploreScreen TEXT,
ruleExplore TEXT,
searchUrl TEXT,
ruleSearch TEXT,
ruleBookInfo TEXT,
ruleToc TEXT,
ruleContent TEXT
);
CREATE INDEX index_book_sources_bookSourceUrl ON bookSources(bookSourceUrl);
```
---
## Required Fields Summary
### BookSource (minimum viable)
- `bookSourceUrl` — Primary Key
- `bookSourceName` — Display name
- `searchUrl` — Search URL template
- `ruleSearch` — Search rules
- `ruleBookInfo` — Book info rules
- `ruleToc` — TOC rules
- `ruleContent` — Content rules
### ruleSearch (minimum)
- `bookList` — List container selector
- `name` — Book name selector
- `bookUrl` — Book URL selector
### ruleToc (minimum)
- `chapterList` — Chapter list selector
- `chapterName` — Chapter name selector
- `chapterUrl` — Chapter URL selector
### ruleContent (minimum)
- `content` — Content selector
### Forbidden Fields
- **NO** `prevContentUrl` — does not exist in Legado source code
- **NO** `:contains()` — use `text.关键词`
- **NO** `:first-child` / `:last-child` — use `.0` / `.-1`
---
## Key Source Code Findings
1. **No `prevContentUrl`**: Confirmed nonexistent in ContentRule.kt
2. **Rule storage**: All rules stored as TEXT, converted via GSON
3. **Caching**: String rules, regex, and scripts all cached in HashMaps
4. **Multiple format support**: Rules support both JSON object and primitive string
5. **Regex caching**: Compiled regex cached for performance
6. **WebView integration**: Dynamic content via `webJs` and `BackstageWebView`
7. **JSON deserialization**: Custom deserializers handle flexible input formats
FILE:references/legado_development_guide.md
# Legado Book Source Development Guide
## Table of Contents
1. [Workflow Details](#workflow-details)
2. [Encoding Detection & Handling](#encoding)
3. [HTML Structures & Solutions](#html-structures)
4. [nextContentUrl Decision Rules](#nextcontenturl)
5. [Regular Expression Formats](#regex)
6. [Troubleshooting](#troubleshooting)
7. [Best Practices](#best-practices)
8. [Advanced Features](#advanced-features)
---
## Workflow Details
### Phase 1 Detail: Information Collection
**1.1 Browser Developer Tools (获取真实请求)**
Record from Network panel:
- Request method (GET/POST)
- Full request URL
- All request headers
- Request body (for POST)
- Query parameters
- Response status code & Content-Type
**1.2 JavaScript File Analysis**
Identify key JS files: `search.js`, `common.js`, `app.js`
Extract from JS:
- Search function call patterns
- Parameter generation logic (encryption, signing)
- API endpoint configuration
- Request header configuration
- Encoding methods
```bash
cd tools && python js_param_analyzer.py
```
**1.3 Real HTML Acquisition**
Must fetch: homepage, search results, book detail, TOC, content page.
Save requirements: complete HTML, correct encoding, all `<script>` and `<link>` tags.
### Phase 1 Detail: User Inquiry for Uncertainty
**Scenario 1: Unknown search interface parameters**
Ask user: open DevTools → Sources → search for `signature`, `token`, `encrypt`, `sign`. Get related JS code.
**Scenario 2: Complex HTML / anti-scraping**
Ask user: check Network XHR/Fetch for API requests, or use headless browser.
**Scenario 3: Anti-scraping detected**
Ask user: provide login credentials, website docs, or choose another site.
---
## Encoding Detection & Handling
**Critical: Detect BEFORE fetching HTML!**
```python
detected_charset = detect_charset(url="http://example.com")
# Then use in all requests:
smart_fetch_html(url="...", charset=detected_charset)
```
**Encoding rules:**
- UTF-8 → omit charset parameter (default)
- GBK → add `"charset":"gbk"` to POST/GET requests
- GB2312 → use `"charset":"gbk"` (GBK compatible)
**searchUrl encoding example:**
```
UTF-8: /search?q={{key}}
GBK: /search,{"method":"POST","body":"key={{key}}","charset":"gbk"}
```
---
## HTML Structures & Solutions
### Structure 1: Standard List (with cover)
```html
<div class="book-list">
<div class="item">
<img src="cover.jpg" class="cover"/>
<a href="/book/1" class="title">Book Name</a>
</div>
</div>
```
```json
{"ruleSearch": {"bookList": ".book-list .item", "name": ".title@text", "bookUrl": "a@href", "coverUrl": "img@src"}}
```
### Structure 2: Search Page (no cover, merged info)
```html
<div class="hot_sale">
<a href="/book/1">
<p class="title">Book Name</p>
<p class="author">Category | Author: Name</p>
</a>
</div>
```
```json
{"ruleSearch": {"bookList": ".hot_sale", "name": ".title@text", "author": ".author.0@text##.*| |Author:##", "kind": ".author.0@text##\\|.*##", "bookUrl": "a@href", "coverUrl": ""}}
```
### Structure 3: Lazy Loading Images
```html
<img class="lazy" data-original="cover.jpg" src="placeholder.jpg"/>
```
```json
{"coverUrl": "img.lazy@data-original||img@src"}
```
---
## nextContentUrl Decision Rules
**Core: Set ONLY for true next chapter links.**
| Scenario | Button Text | URL Pattern | Action |
|----------|------------|-------------|--------|
| True next chapter | "下一章", "下章" | /ch/1 → /ch/2 | **SET** |
| Same chapter pagination | "下一页", "继续阅读" | /ch/1_1 → /ch/1_2 | **LEAVE EMPTY** |
| Ambiguous | "下一", "下页" | Check URL | Compare URLs |
---
## Regular Expression Formats
**Format 1: Delete matched content**
```
选择器@提取类型##正则表达式
```
**Format 2: Replace matched content**
```
选择器@提取类型##正则表达式##替换内容
```
**Format 3: Extract using capture groups**
```
选择器@提取类型##正则表达式(捕获组)##$1
```
**Examples:**
```json
{
"author": ".author@text##.*Author:##",
"author": ".author@text##Author:(.*)##$1",
"content": "#content@html##<div id=\"ad\">[\\s\\S]*?</div>|Please bookmark##"
}
```
---
## Troubleshooting
| Problem | Solution |
|---------|----------|
| Search no results | Verify CSS selectors; check if structure changed; add headers/cookies |
| Content missing | Check dynamic loading; set cookies; add login logic |
| Directory order wrong | Add `-` before list selector: `-ul.chapter-list li` |
| Advertisements | Use `@ownText` instead of `@text`; clean with regex |
| Pagination | `nextContentUrl` for content; `nextTocUrl` for TOC |
| Image hotlinking | Configure Referer header in `header` field |
| Encoding (garbled) | Detect encoding before fetching; use in all requests |
---
## Best Practices
1. Always query knowledge base first
2. Detect encoding before fetching HTML
3. Reference real examples (134 real book sources)
4. Validate with real HTML — never assume structure
5. Handle special cases: no cover, lazy loading, merged info
6. Use proven templates
7. Test thoroughly
8. Use `knowledge_learner` for continuous improvement
9. Use `user_intervention` for complex scenarios
---
## Advanced Features
### Rule Processing Modes (AnalyzeRule.kt)
- **Default** → JSoup CSS selectors
- **Json** → JSONPath
- **XPath** → XPath selectors
- **Js** → JavaScript evaluation
- **WebJs** → WebView JavaScript
### Advanced BookSource Fields
- `checkKeyWord` — search validation keyword
- `subContent` — secondary content appended to main content
- `title` — extract title from content page
- `payAction` — purchase action (JS or URL with `{{js}}`)
- `callBackJs` — event listener callback JS
- `preUpdateJs` — JS executed before TOC update
- `formatJs` — formatting JS
- `imageStyle` — image display style (default centered, FULL=max width)
- `imageDecode` — image bytes decryption JS
- `webJs` — WebView JS injection for dynamic content
- `coverDecodeJs` — cover image decryption JS
- `loginCheckJs` — login detection JS
- `exploreScreen` — discovery filter rules
### Caching Mechanisms
- String rules → `stringRuleCache` HashMap
- Regular expressions → `regexCache` HashMap
- Scripts → `scriptCache` HashMap
### WebSocket Debug Interface
- URL: `ws://127.0.0.1:1235/bookSourceDebug`
- Message: `{key: "搜索关键词", tag: "源链接"}`
### Learning & Auditing
- `knowledge_learner` — learn from new book sources
- `knowledge_applier` — apply learned patterns
- `knowledge_enhanced_analyzer` — enhanced analysis with knowledge
- `audit_knowledge_base` — validate knowledge base integrity
- `knowledge_auditor` — audit specific items
FILE:references/no_python_workflow.md
# 无 Python 环境工作流(MCP 工具优先)
当系统没有 Python 环境时,优先检查宿主系统已配置的 MCP 工具,利用它们完成书源开发全流程。
## Step 0: 检查可用 MCP 工具
先扫描当前环境有哪些 MCP 工具可用:
```
# 检查是否有文件系统 MCP
list_files / read_file / write_file
# 检查是否有浏览器 MCP
browser_navigate / browser_snapshot / browser_screenshot
# 检查是否有代码执行 MCP
execute_command / run_code / run_python
# 检查是否有 HTTP/网络 MCP
http_get / fetch / curl
```
**根据可用工具选择对应方案。**
## 方案 A: 有浏览器 MCP
浏览器 MCP 是最佳选择,能获取真实渲染后的页面。
### 获取页面源码
```
# 打开搜索页
browser_navigate(url="https://example.com/search?q=测试")
# 获取页面快照(含HTML结构)
browser_snapshot()
# 截图辅助分析
browser_screenshot()
```
### 分析搜索接口
```
# 打开开发者面板方式:先导航到搜索页,执行搜索,查看网络请求
browser_navigate(url="https://example.com")
# 触发搜索操作后
browser_snapshot() # 查看实际发出的请求
```
### 获取多页面
```
browser_navigate(url="https://example.com/book/123") # 详情页
browser_navigate(url="https://example.com/book/123/toc") # 目录页
browser_navigate(url="https://example.com/book/123/ch/1") # 内容页
```
## 方案 B: 有 HTTP/网络 MCP
直接用网络工具获取源码。
### 获取 HTML
```
http_get(url="https://example.com/search?q=测试")
http_get(url="https://example.com/book/123")
http_get(url="https://example.com/book/123/toc")
http_get(url="https://example.com/book/123/ch/1")
```
### POST 请求(搜索接口)
```
http_post(
url="https://example.com/api/search",
headers={"Content-Type": "application/json"},
body='{"keyword":"测试","page":1}'
)
```
### 获取 JS 文件
```
http_get(url="https://example.com/static/search.js")
```
## 方案 C: 有代码执行 MCP
用代码执行环境运行分析脚本。
### 直接运行 tools/ 下的脚本
```
# 如果 MCP 支持执行 shell 命令
execute_command("cd skills/legado-book-source-developer/tools && python3 analyze_url.py https://example.com")
# 或 bash 版(无需 Python)
execute_command("cd skills/legado-book-source-developer/tools && bash analyze_url.sh https://example.com")
```
### 内联代码分析
```
run_python(code="""
import requests
from bs4 import BeautifulSoup
r = requests.get('https://example.com/search?q=test')
soup = BeautifulSoup(r.text, 'html.parser')
print(soup.title)
for item in soup.select('.book-list .item'):
print(item.select_one('.title').text)
""")
```
## 方案 D: 有文件系统 MCP + 浏览器
组合使用:浏览器抓取 → 文件系统存储 → 代码执行分析。
```
# 1. 浏览器获取页面
browser_navigate(url="https://example.com/search?q=测试")
html = browser_snapshot()
# 2. 存储到文件
write_file(path="search_result.html", content=html)
# 3. 如果有代码执行,运行分析
execute_command("grep -o 'class=\"[^\"]*\"' search_result.html | sort -u | head -20")
```
## 通用流程(不论哪种方案)
### 1. 获取 5 个关键页面
- 首页
- 搜索结果页(含关键词)
- 书籍详情页
- 目录页
- 章节内容页
### 2. 识别搜索接口
- URL 模式(GET/POST)
- 参数名和格式
- 是否需要签名/加密
### 3. 推断 CSS 选择器
从获取的 HTML 中找:
- 列表容器(多个重复子元素)
- 书名、作者、封面、链接的位置
- 内容区域的定位
### 4. 参考知识库
```
search_knowledge("CSS选择器格式 提取类型 @text @href @src")
get_real_book_source_examples(limit=5)
get_book_source_templates(limit=3)
```
### 5. 验证并创建书源
在宿主系统中导入 JSON 测试,或直接调用:
```
edit_book_source(complete_source='[{"bookSourceName":"...", ...}]')
```
## 注意事项
- **编码问题**: 浏览器 MCP 自动处理编码;HTTP MCP 需检查 Content-Type
- **动态页面**: 如果页面内容由 JS 渲染,必须用浏览器 MCP(HTTP MCP 只拿到模板)
- **反爬虫**: 浏览器 MCP 天然绕过大部分反爬;HTTP MCP 需手动设 Headers/Cookie
- **大文件**: 设置合理的截断限制,避免上下文溢出
FILE:references/quick_search_url_extractor_使用说明.md
# 快速搜索地址提取工具使用指南
## 概述
`quick_search_url_extractor.py` 是基于"快速写源"订阅源核心技术的智能工具,能够自动识别和生成网站的搜索地址。
## 功能特性
### 1. 智能表单识别
- 自动扫描页面中的所有`<form>`表单
- 提取表单的`action`、`method`属性
- 分析所有输入字段
### 2. 多策略搜索字段识别
工具使用四种策略来识别搜索关键词字段:
**策略1:单一字段**
```javascript
if (names.length == 1) {
value = "{{key}}"
}
```
如果表单只有一个输入字段,自动识别为搜索字段。
**策略2:常见字段名**
```javascript
['q', 'wd', 'query', 'search', 'key', 'keyword', 'k', 's']
```
匹配这些常见的搜索字段名。
**策略3:字段名包含关键词**
```javascript
if ('search' in name || 'key' in name) {
return name
}
```
识别字段名中包含"search"或"key"的字段。
**策略4:文本类型且无默认值**
```javascript
if (field['type'] == 'text' && !field['value']) {
return field['name']
}
```
识别文本类型且没有默认值的字段。
### 3. 自动生成搜索地址
**GET请求:**
```
/search?q={{key}}
```
**POST请求:**
```
/search,{"method":"POST","body":"key={{key}}&page=1"}
```
**带编码配置:**
```
/search,{"method":"POST","body":"key={{key}}","charset":"GBK"}
```
### 4. 发现规则自动提取
自动识别网站上的分类、排行、推荐等链接:
```javascript
['sort', 'list', 'rank', 'tag', 'shuku', 'fenlei', 'Soft', 'allvisit', 'paihang', 'quanben']
```
### 5. 书源草稿生成
自动生成包含OG元数据的书源框架:
```json
{
"bookSourceName": "快速生成的书源_1773468883",
"bookSourceUrl": "https://example.com",
"searchUrl": "/search?q={{key}}",
"ruleBookInfo": {
"name": "[property=\"og:novel:book_name\"]@content",
"author": "[property=\"og:novel:author\"]@content",
"coverUrl": "[property=\"og:image\"]@content",
"intro": "[property=\"og:description\"]@content"
}
}
```
## 使用方法
### 基本用法
```bash
# 进入tools目录
cd tools
# 运行工具
python quick_search_url_extractor.py https://www.example.com
```
### 示例输出
```
======================================================================
快速搜索地址提取工具
======================================================================
目标网站: https://www.example.com
正在获取: https://www.example.com
✓ 状态码: 200
✓ 编码: UTF-8
✓ 大小: 45231 字符
找到 2 个表单
--- 表单 1 ---
Action: https://www.example.com/search
Method: POST
字段数: 3
- q (type=text, value=, placeholder=请输入关键词)
- type (type=hidden, value=novel)
- page (type=hidden, value=1)
✓ 识别为搜索表单!
✓ 搜索字段: q
✓ 搜索地址: https://www.example.com/search,{"method":"POST","body":"q={{key}}&type=novel&page=1"}
发现 15 个潜在的发现规则链接
======================================================================
分析结果
======================================================================
✓ 成功识别 1 个搜索表单
表单 1:
搜索字段: q
搜索地址: https://www.example.com/search,{"method":"POST","body":"q={{key}}&type=novel&page=1"}
✓ 发现规则 (5 个):
- 玄幻小说: /xuanhuan
- 都市小说: /dushi
- 科幻小说: /kehuan
- 排行榜: /rank
- 全本小说: /quanben
✓ 书源草稿已生成
草稿文件: book_source_draft_1773468883.json
⚠️ 注意: 书源草稿需要您补充以下内容:
1. ruleSearch 的所有规则
2. ruleToc 的所有规则
3. ruleContent 的所有规则
======================================================================
分析完成!
======================================================================
```
## 高级用法
### 自定义请求头
如果网站需要特定的请求头,可以修改脚本中的 `headers` 参数:
```python
headers = {
'User-Agent': 'Mozilla/5.0 (Linux; Android 8.0.0; wv)...',
'Referer': 'https://www.example.com',
'X-Requested-With': 'XMLHttpRequest'
}
extractor = QuickSearchUrlExtractor(url, headers=headers)
```
### Cloudflare过盾
如果检测到Cloudflare验证,可以使用内置浏览器:
```python
# 修改 fetch_page 方法
if webSrc.match(/Just a moment/){
try {
webSrc = java.startBrowserAwait(url, "验证", false).body();
} catch(e) {
webSrc = java.startBrowserAwait(url, "验证").body();
}
}
```
### 处理动态加载
如果网站使用JavaScript动态渲染,需要:
1. 使用浏览器开发者工具分析网络请求
2. 提取实际的API接口
3. 手动配置搜索地址
## 输出文件
工具会生成以下文件:
```
tools/
├── book_source_draft_时间戳.json # 书源草稿
└── analysis_report_时间戳.json # 分析报告(如果扩展)
```
## 书源草稿说明
生成的书源草稿包含:
1. **基础信息**
- 书源名称
- 书源URL
- 搜索地址(自动生成)
2. **OG元数据规则**(如果页面有)
- 书名:`[property="og:novel:book_name"]@content`
- 作者:`[property="og:novel:author"]@content`
- 封面:`[property="og:image"]@content`
- 简介:`[property="og:description"]@content`
3. **发现规则**(如果检测到)
- 分类链接
- 排行榜链接
- 推荐链接
## 需要补充的内容
书源草稿生成后,您需要补充:
### 1. 搜索规则
```json
"ruleSearch": {
"bookList": ".book-list .item", // 需要补充
"name": ".title@text", // 需要补充
"author": ".author@text", // 需要补充
"kind": ".category@text", // 需要补充
"lastChapter": ".last-chapter@text", // 需要补充
"bookUrl": "a@href", // 需要补充
"coverUrl": "img@src" // 需要补充
}
```
### 2. 目录规则
```json
"ruleToc": {
"chapterList": "#chapter-list li", // 需要补充
"chapterName": "a@text", // 需要补充
"chapterUrl": "a@href", // 需要补充
"nextTocUrl": "" // 需要补充
}
```
### 3. 内容规则
```json
"ruleContent": {
"content": "#content@html", // 需要补充
"nextContentUrl": "" // 需要补充
}
```
## 常见问题
### Q1: 工具找不到搜索表单?
**可能原因:**
1. 网站使用AJAX动态加载
2. 搜索功能是纯JavaScript实现
3. 需要登录才能访问
**解决方法:**
1. 使用浏览器开发者工具查看Network请求
2. 手动分析JavaScript代码
3. 查看页面的API接口
### Q2: 识别的搜索字段不对?
**可能原因:**
1. 表单有多个字段
2. 字段名不符合常见模式
3. 有隐藏字段干扰
**解决方法:**
1. 手动查看表单结构
2. 修改脚本中的识别逻辑
3. 使用浏览器验证
### Q3: POST请求的body不正确?
**可能原因:**
1. 字段值获取错误
2. 有额外的参数
3. Content-Type不对
**解决方法:**
1. 使用浏览器开发者工具复制cURL
2. 对比生成的参数
3. 手动调整body
### Q4: 生成的书源无法搜索?
**检查清单:**
- [ ] searchUrl是否正确
- [ ] 字段名是否正确
- [ ] 编码是否正确(GBK vs UTF-8)
- [ ] 是否需要特殊的请求头
- [ ] 搜索页HTML结构是否分析正确
## 与quick_analyze.py的区别
| 特性 | quick_analyze.py | quick_search_url_extractor.py |
|------|------------------|-------------------------------|
| 主要功能 | 全面分析网站结构 | 快速获取搜索地址 |
| 表单识别 | 基础识别 | 智能多策略识别 |
| 发现规则 | 不支持 | 自动提取 |
| 书源草稿 | 完整草稿 | 框架草稿 |
| 适用场景 | 首次分析 | 快速开始 |
## 最佳实践
### 1. 先用此工具快速开始
```bash
python quick_search_url_extractor.py https://example.com
```
### 2. 检查生成的搜索地址
- 使用浏览器测试搜索地址
- 确认参数是否正确
- 验证返回的HTML
### 3. 补充完整的书源规则
- 使用quick_analyze.py获取更多细节
- 手动分析HTML结构
- 编写完整的CSS选择器
### 4. 验证书源
```bash
python validate_book_source.py book_source_draft_xxx.json
```
## 技术原理
### 表单识别算法
```python
# 1. 查找所有表单
forms = soup.find_all('form')
# 2. 分析每个表单
for form in forms:
action = form.get('action', '')
method = form.get('method', 'GET').upper()
inputs = form.find_all(['input', 'select', 'textarea'])
# 3. 智能识别搜索字段
search_field = identify_search_field(inputs)
# 4. 生成搜索地址
if search_field:
search_url = generate_search_url(action, method, inputs, search_field)
```
### 搜索字段识别策略
```python
def identify_search_field(fields):
# 策略1: 单字段
if len(fields) == 1:
return fields[0]['name']
# 策略2: 常见字段名
for field in fields:
if field['name'] in ['q', 'wd', 'query', 'search', 'key']:
return field['name']
# 策略3: 字段名包含关键词
for field in fields:
if 'search' in field['name'].lower():
return field['name']
# 策略4: 文本类型
for field in fields:
if field['type'] == 'text' and not field['value']:
return field['name']
return None
```
## 更新日志
### v1.0 (2026-03-15)
- 初始版本
- 实现基础表单识别
- 智能搜索字段识别
- 自动生成搜索地址
- 发现规则提取
- 书源草稿生成
## 许可证
本工具遵循与Legado书源开发技能相同的许可证。
## 参考资源
- [快速写源订阅源原理分析](../references/快速写源订阅源原理分析.md)
- [Legado官方文档](https://legado.yuewen.com/)
- [书源开发指南](../QUICKSTART.md)
FILE:references/upload_config_example.json
{
"compress": false,
"downloadUrlRule": "$.data.links.url",
"summary": "自定义图床示例",
"uploadUrl": "https://your-image-host.com/api/upload",
"method": "POST",
"headers": {
"Accept": "application/json",
"Authorization": "Bearer YOUR_TOKEN"
},
"body": {
"file": "fileRequest",
"folder": "book_sources"
},
"type": "multipart/form-data"
}
FILE:references/傲娇的验证大佬v0.2.js
function 傲娇的验证大佬(a){try{java.log("🎮 哼!本大佬今天心情不错,就勉为其难帮你处理这些验证吧~");var r=a.url(),o=a.body(),t=a.code(),e=o&&(403===t||503===t||429===t||200===t&&(o.match(/Just a moment/)||o.match(/Checking your browser/)||o.match(/DDoS protection/))),n=o&&(o.includes("/_guard/")||o.match(/拖动滑块完成验证/)||o.match(/滑动验证/));if(java.log("🖼让我先看看现状:\n状态码:"+t+"\n当前url:"+r),!e&&!n)return java.log("✅ 哼~ 一个能打的小妖精都没有,直接通过!(⑅˃◡˂⑅)"),a;if(e){var c=parseInt(source.get("cf_block_count")||"0");if(c=isNaN(c)?1:c+1,source.put("cf_block_count",c.toString()),java.log("🚨 发现CloudFlare小妖精!这是第 "+c+" 次跟它玩了~"),c<=3){java.log("😤 尝试用WebView驯服它...");for(var u=0;u<=1;u++)try{var l=java.webView(r,r,"setTimeout(function() { window.legado.getHTML(document.documentElement.outerHTML); }, 5000);");if(l&&!l.match(/Just a moment|Checking your browser|DDoS protection/)&&200===(a=java.connect(r)).code())return source.put("cf_block_count","0"),java.log("✅ 哼!CloudFlare小妖精被本大佬驯服了!"),a}catch(a){java.log("💢 WebView驯服失败记录:第"+(u+1)+"次尝试,错误:"+a)}}return java.log("🤖 调教失败,需要手动操作了,真麻烦!"),java.toast("需要CloudFlare验证,请在浏览器中完成验证"),java.startBrowserAwait(r,"CloudFlare验证"),source.put("cf_block_count","0"),a=java.connect(r)}if(n){var v=parseInt(source.get("slider_block_count")||"0");v=isNaN(v)?1:v+1,source.put("slider_block_count",v.toString()),java.log("🎯 发现滑块小怪兽!这是第 "+v+" 次跟它玩了~");var currentTestMode=source.put("test_mode","auto"),defaultTrack={move:[{timestamp:1759552171921,x:61,y:294.5},{timestamp:1759552171983,x:102.39546966552734,y:295.3030700683594},{timestamp:1759552172001,x:127.83409881591797,y:296.16668701171875},{timestamp:1759552172019,x:150.73541259765625,y:299.95098876953125},{timestamp:1759552172040,x:176.0093536376953,y:302.16729736328125},{timestamp:1759552172051,x:202.0565185546875,y:303.8584899902344},{timestamp:1759552172068,x:230.5967559814453,y:302.4824523925781},{timestamp:1759552172084,x:256.02685546875,y:302},{timestamp:1759552172100,x:279.3731994628906,y:302.717529296875},{timestamp:1759552172118,x:299.8751220703125,y:303.5597839355469},{timestamp:1759552172135,x:318.1665344238281,y:304},{timestamp:1759552172151,x:343.1929626464844,y:305.7620544433594}],btn:52,slider:328,page_width:360,page_height:606},testMode=currentTestMode;java.log("🔧 当前测试模式:"+testMode),"clear"===testMode?(source.put("success_track",""),source.put("track_history","[]"),source.put("track_stats","{}"),testMode="auto",source.put("test_mode",testMode),java.log("🗑️ 已清空所有珍藏数据!模式切换为:"+testMode)):"reset"===testMode&&(source.put("success_track",JSON.stringify(defaultTrack)),testMode="auto",source.put("test_mode",testMode),java.log("🔄 已恢复默认珍藏轨迹!模式:"+testMode));var savedTrackStr=source.get("success_track"),savedTrack=null;if(savedTrackStr&&""!==savedTrackStr&&"null"!==savedTrackStr)try{savedTrack=JSON.parse(savedTrackStr),java.log("📂 发现本大佬珍藏的成功轨迹")}catch(err){java.log("⚠️ 珍藏轨迹已损坏:"+err),savedTrack=null,"auto"==testMode&&(source.put("success_track",JSON.stringify(defaultTrack)),savedTrack=defaultTrack,java.log("🔄 auto模式下轨迹损坏,已重新初始化"))}else"auto"===testMode?(source.put("success_track",JSON.stringify(defaultTrack)),savedTrack=defaultTrack,java.log("💾 auto模式下无保存轨迹,已初始化默认轨迹")):(savedTrack=null,java.log("🎲 "+testMode+"模式下,不使用默认轨迹"));var trackHistoryStr=source.get("track_history")||"[]",trackHistory=[];try{trackHistory=JSON.parse(trackHistoryStr),Array.isArray(trackHistory)||(trackHistory=[]),java.log("📚 轨迹历史记录:"+trackHistory.length+"条")}catch(err){trackHistory=[],java.log("📚 轨迹历史记录解析失败,使用空数组")}var g=cookie.getKey(r,"guard");if(!g){var i=o.match(/guard=([^;&]+)/)||o.match(/"guard"\s*:\s*"([^"]+)"/);g=i&&i[1]}if(!g)return java.log("❌ 没找到guard令牌,滑块小怪兽藏起来了!"),java.toast("需要滑块验证,请在浏览器中完成"),java.startBrowserAwait(r,"滑块验证"),source.put("slider_block_count","0"),java.connect(r);var s=g.substr(0,8),j=java.md5Encode(s),d=java.hexDecodeToByteArray(j),h=null;try{if(!(h=java.createSymmetricCrypto("AES/CBC/PKCS7Padding",d,d)))throw new Error("加密器创建失败,返回null")}catch(a){return java.log("🔐 加密器创建失败: "+a),java.log("💔 滑块小怪兽的防御太强了,本大佬需要人类帮忙!"),java.toast("滑块验证需要手动完成"),cookie.removeCookie(r),source.put("slider_block_count","0"),java.startBrowserAwait(r,"滑块验证")}for(var m="没有失败记录呢~看来本大佬的第一次尝试就被小怪兽打败了!(>﹏<)",f=1;f<=3;f++){var trackData=null,trackSource="";if("random"===testMode)trackData={move:generateRandomTrack(randomParam=f),btn:52,slider:328,page_width:360,page_height:606},trackSource="随机模式(参数"+randomParam+")";else if("auto"==testMode&&1==f&&savedTrack)trackData=savedTrack,trackSource="珍藏轨迹",java.log("🎯 自动模式:首次尝试使用珍藏轨迹");else{var randomParam;trackData={move:generateRandomTrack(randomParam=f-1),btn:52,slider:328,page_width:360,page_height:606},trackSource="随机轨迹(参数"+randomParam+")"}java.log("📊 使用轨迹来源:"+trackSource+",轨迹点数:"+(trackData.move?trackData.move.length:0));var _={move:trackData.move,btn:trackData.btn,slider:trackData.slider,page_width:trackData.page_width,page_height:trackData.page_height},k=null;try{var p=JSON.stringify(_);if(!(k=h.encryptBase64(p))||0===k.length)throw new Error("加密结果为空")}catch(a){if(java.log("🔐 第"+f+"次加密失败: "+a),m="💔 第"+f+"次加密失败!\n错误信息:"+a+"\n轨迹来源:"+trackSource+"\n哼!滑块小怪兽的加密陷阱太狡猾了!(•̀へ•́╮)",java.log(m),f<3){java.log("🛡️ 准备下一次攻击!");continue}}var w={headers:{cookie:"guard="+g+"; guardret="+k,"User-Agent":java.getWebViewUA(),Referer:r}};java.log("轨迹来源:"+trackSource+"\n🚀 向滑块小怪兽发起攻击!");try{var b=java.ajax(r+","+JSON.stringify(w));if(b&&!b.includes("/_guard/")){java.log("🎊 第 "+f+" 次尝试成功!搞定滑块小怪兽!"),("auto"==testMode&&"珍藏轨迹"!==trackSource||"random"!==testMode)&&source.put("success_track",JSON.stringify(trackData));var trackEntry={source:trackSource,data:trackData,time:(new Date).getTime(),guard:g?g.length>10?g.substring(0,10)+"...":g:"无"};trackHistory.unshift(trackEntry),trackHistory.length>10&&(trackHistory=trackHistory.slice(0,10)),source.put("track_history",JSON.stringify(trackHistory));var trackStatsStr=source.get("track_stats")||"{}",trackStats={};try{trackStats=JSON.parse(trackStatsStr)}catch(e){}return trackStats[trackSource]=(trackStats[trackSource]||0)+1,trackStats.total_success=(trackStats.total_success||0)+1,trackStats.last_success_time=(new Date).getTime(),trackStats.last_success_source=trackSource,trackStats.current_mode=testMode,source.put("track_stats",JSON.stringify(trackStats)),cookie.removeCookie(r),source.put("slider_block_count","0"),java.key&&java.initUrl(),java.getStrResponse()}m="💔 第"+f+"次失败!\n轨迹来源:"+trackSource+"\n小怪兽回应了:"+(b?"有内容但包含_guard":"空响应")+"\n哼!本大佬下次一定行!(╯°□°)╯︵ ┻━┻",java.log(m)}catch(a){var y=g?g.length>20?g.substring(0,20)+"...":g:"未找到";m="💥 第"+f+"次尝试发生异常!\n错误信息:"+a+"\n轨迹来源:"+trackSource+"\nguard令牌:"+y+"\n可恶的小怪兽,居然用异常攻击本大佬!(•̀へ•́╮)",java.log(m)}f<3&&java.log("💢 换个姿势再来一次!")}if(trackStatsStr=source.get("track_stats"))try{var stats=JSON.parse(trackStatsStr);for(var key in stats)"last_success_time"!==key&&"last_success_source"!==key&&java.log(" "+key+": "+stats[key]+"次");if(stats.last_success_time){var lastTime=new Date(stats.last_success_time);java.log(" 上次成功时间: "+lastTime.toLocaleString())}}catch(e){}if(trackHistory.length>0){java.log("📚 轨迹历史记录(最近"+trackHistory.length+"条):");for(var hi=0;hi<Math.min(trackHistory.length,3);hi++){var hist=trackHistory[hi],timeStr=new Date(hist.time).toLocaleString();java.log(" "+(hi+1)+". "+hist.source+" - "+timeStr)}trackHistory.length>3&&java.log(" ...还有"+(trackHistory.length-3)+"条记录")}return java.log("🔥🔥🔥 自动验证失败记录 🔥🔥🔥"),java.log("最后一次失败详情:"),java.log(m),java.log("总失败次数:"+v+"次"),y=g?g.length>30?g.substring(0,30)+"...":g:"未找到",java.log("guard令牌:"+y),java.log("💔 失败了!滑块小怪兽太狡猾了!本大佬需要人类帮忙!"),java.toast("滑块验证失败,需要手动验证"),cookie.removeCookie(r),source.put("slider_block_count","0"),java.startBrowserAwait(r,"滑块验证")}return a}catch(r){java.log("💥 发生错误: "+r+",但本大佬已经处理了~");try{java.log("📝 错误详情:\n"+(r.stack||r))}catch(a){java.log("📝 无法获取错误堆栈")}return a}}function generateRandomTrack(a){var r=294.5,o=343.1929626464844,e=Math.floor(11*Math.random())+5;2===a&&(e=Math.floor(11*Math.random())+5);var n=[],c=(new Date).getTime();n.push({timestamp:c-Math.floor(7*Math.random())-18,x:61,y:r});for(var u=o-61,l=1;l<=e;l++){var v=l/(e+1);isNaN(v)&&(v=.5);var g=61+v*u,i=r+11.262054443359375*v;(isNaN(g)||isNaN(i))&&(g=61,i=r);var s=(Math.random()-.5)*(1===a?2:3),j=(Math.random()-.5)*(1===a?1:2);isNaN(s)&&(s=0),isNaN(j)&&(j=0);var d=Math.floor(7*Math.random())+18;isNaN(d)&&(d=20),c+=d,n.push({timestamp:c,x:g+s,y:i+j})}var h=Math.floor(7*Math.random())+18;return isNaN(h)&&(h=20),c+=h,n.push({timestamp:c,x:o,y:305.7620544433594}),n}result=傲娇的验证大佬(result);
FILE:references/工具使用说明.md
# 书源开发工具使用说明
> **更新时间**: 2026-03-13
> **版本**: v2.1
---
## 📦 工具列表
### 核心工具
| 工具 | 说明 | 用途 |
|------|------|------|
| `quick_analyze.py` | 快速网站分析工具 ⭐ | 自动分析网站并生成符合JSON格式的书源 |
| `js_param_analyzer.py` | JavaScript参数分析工具 | 分析网站JavaScript代码,提取API信息 |
| `validate_book_source.py` | 书源JSON验证工具 ⭐ | 验证书源JSON是否符合Legado标准格式 |
| `upload_book_source.py` | 书源直链上传工具 ⭐ | 上传书源到图床,生成可分享的直链 |
### 传统工具
| 工具 | 说明 | 用途 |
|------|------|------|
| `analyze_fhysc.py` | 完整的网站分析工具 | 传统分析流程,分步骤执行 |
| `get_book_detail.py` | 获取书籍详情 | 获取详情页、提取章节列表 |
| `get_chapter.py` | 获取章节内容 | 获取章节HTML、提取正文 |
---
## ⭐ quick_analyze.py - 快速网站分析工具
### 功能特性
✅ **自动检测网站编码**(UTF-8/GBK)
✅ **自动下载并分析JavaScript文件**
✅ **自动搜索可能的搜索接口模式**
✅ **自动分析HTML结构**
✅ **自动测试搜索接口**
✅ **自动生成符合JSON格式的书源**
✅ **自动保存HTML到html_storage**
✅ **自动生成元数据JSON**
### 使用方法
```bash
# 基本用法
cd tools
python quick_analyze.py https://www.example.com
# 示例
python quick_analyze.py https://www.bqg.com
python quick_analyze.py https://m.qishu99.cc
```
### 输出文件
工具会生成以下文件:
1. **书源JSON文件**
- 文件名: `book_source_时间戳.json`
- 格式: 符合Legado标准的JSON数组
- 示例: `book_source_1678901234.json`
2. **分析报告**
- 文件名: `analysis_report_时间戳.json`
- 内容: 完整的分析结果
- 示例: `analysis_report_1678901234.json`
3. **HTML文件**(存储在 `references/html_storage/`)
- 文件名: `{md5_hash}.html`
- 示例: `8289e449cf5e5d694e113e9b846a7c0e.html`
4. **HTML元数据**(存储在 `references/html_storage/`)
- 文件名: `{md5_hash}.meta.json`
- 内容: URL、大小、时间戳等
- 示例: `8289e449cf5e5d694e113e9b846a7c0e.meta.json`
5. **JavaScript文件**(存储在 `references/html_storage/`)
- 文件名: `{md5_hash}.js`
- 示例: `abc123def456.js`
### 执行流程
```
1. 检测网站编码并保存首页
↓
2. 查找并下载JavaScript文件(最多10个)
↓
3. 分析搜索接口模式(表单、链接)
↓
4. 分析HTML结构(书籍列表、选择器)
↓
5. 生成符合JSON格式的书源
↓
6. 保存分析报告
```
### 生成的书源JSON格式
```json
[
{
"bookSourceUrl": "https://www.example.com",
"bookSourceName": "示例书源_www_example_com",
"bookSourceType": 0,
"bookSourceGroup": "默认分组",
"enabled": true,
"enabledExplore": true,
"enabledCookieJar": true,
"loginUrl": "",
"loginUi": "",
"loginCheckJs": "",
"concurrentRate": "",
"header": "{\"User-Agent\":\"Mozilla/5.0...\"}",
"searchUrl": "/search?q={{key}}",
"exploreUrl": "",
"ruleSearch": {
"bookList": ".book-list",
"name": ".title@text",
"author": ".author@text",
"kind": "",
"wordCount": "",
"lastChapter": "",
"intro": "",
"coverUrl": "img@src",
"bookUrl": "a@href"
},
"ruleBookInfo": {
"name": "",
"author": "",
"kind": "",
"wordCount": "",
"lastChapter": "",
"intro": "",
"coverUrl": "",
"init": ""
},
"ruleToc": {
"chapterList": "",
"chapterName": "",
"chapterUrl": "",
"formatJs": "",
"nextTocUrl": ""
},
"ruleContent": {
"content": "",
"replaceRegex": "",
"imageStyle": "",
"imageDecode": "",
"webJs": "",
"nextContentUrl": "",
"title": ""
},
"ruleExplore": {},
"ruleReview": {},
"bookSourceComment": "由quick_analyze.py自动生成\\nURL: https://www.example.com\\n编码: UTF-8",
"variableComment": "{}"
}
]
```
### html_storage 结构
```
references/html_storage/
├── 8289e449cf5e5d694e113e9b846a7c0e.html # HTML文件
├── 8289e449cf5e5d694e113e9b846a7c0e.meta.json # HTML元数据
├── abc123def456.js # JavaScript文件
├── abc123def456.js.meta.json # JS元数据
└── ...
```
### 元数据JSON格式
```json
{
"url": "http://m.gashuw.com/biquge_168070/",
"size": 11041,
"timestamp": 1771407694.9349854,
"datetime": "2026-03-13T10:34:54.934985",
"page_type": "home",
"storage_path": "references/html_storage/8289e449cf5e5d694e113e9b846a7c0e.html"
}
```
---
## ⭐ validate_book_source.py - 书源JSON验证工具
### 功能特性
✅ **验证JSON格式**
✅ **验证必需字段**
✅ **验证字段类型**
✅ **验证规则对象**
✅ **生成详细错误报告**
### 使用方法
```bash
# 验证书源JSON
cd tools
python validate_book_source.py book_source.json
# 示例
python validate_book_source.py book_source_1678901234.json
```
### 验证规则
#### 1. 格式验证
- 必须是JSON数组格式
- 数组不能为空
- 每个书源必须是对象类型
#### 2. 必需字段验证
**书源级别必需字段**(23个):
```json
{
"bookSourceUrl": "https://www.example.com",
"bookSourceName": "示例书源",
"bookSourceType": 0,
"bookSourceGroup": "默认分组",
"enabled": true,
"enabledExplore": true,
"enabledCookieJar": true,
"loginUrl": "",
"loginUi": "",
"loginCheckJs": "",
"concurrentRate": "",
"header": "",
"searchUrl": "/search",
"exploreUrl": "",
"ruleSearch": {},
"ruleBookInfo": {},
"ruleToc": {},
"ruleContent": {},
"ruleExplore": {},
"ruleReview": {},
"bookSourceComment": "",
"variableComment": "{}"
}
```
**ruleSearch必需字段**(9个):
```json
{
"bookList": ".book-list",
"name": ".title@text",
"author": ".author@text",
"kind": "",
"wordCount": "",
"lastChapter": "",
"intro": "",
"coverUrl": "img@src",
"bookUrl": "a@href"
}
```
**ruleToc必需字段**(5个):
```json
{
"chapterList": "#chapter-list li",
"chapterName": "a@text",
"chapterUrl": "a@href",
"formatJs": "",
"nextTocUrl": ""
}
```
**ruleContent必需字段**(7个):
```json
{
"content": "#content@html",
"replaceRegex": "",
"imageStyle": "",
"imageDecode": "",
"webJs": "",
"nextContentUrl": "",
"title": ""
}
```
### 验证输出示例
```
======================================================================
验证书源JSON: book_source.json
======================================================================
✓ 找到 1 个书源
--- 验证书源 1/1 ---
书源名称: 示例书源
书源URL: https://www.example.com
bookList: .book-list
bookUrl: a@href
name: .title@text
author: .author@text
coverUrl: img@src
======================================================================
验证结果
======================================================================
✓ 验证通过!书源JSON格式正确
```
### 错误示例
```
======================================================================
验证书源JSON: invalid_source.json
======================================================================
✗ JSON格式错误: Expecting value: line 1 column 1 (char 0)
======================================================================
验证结果
======================================================================
✗ 发现 1 个错误:
- JSON格式错误: Expecting value: line 1 column 1 (char 0)
✗ 验证失败!请修复上述错误
```
---
## ⭐ upload_book_source.py - 书源直链上传工具
### 功能特性
✅ **一键上传书源到图床**
✅ **生成可分享的直链**
✅ **美化返回结果**
✅ **支持自定义上传配置**
✅ **自动提取下载链接**
### 使用方法
```bash
# 基本用法(使用默认配置)
cd tools
python upload_book_source.py <书源JSON文件路径>
# 示例
python upload_book_source.py book_source_1678901234.json
# 使用自定义上传配置
python upload_book_source.py book_source_1678901234.json custom_upload_config.json
```
### 默认上传配置
工具内置了默认的上传配置(鲸落图床):
```json
{
"compress": false,
"downloadUrlRule": "$.data.links.url",
"summary": "鲸落图床",
"uploadUrl": "https://tu.406np.xyz/api/v1/upload",
"method": "POST",
"headers": {
"Accept": "application/json"
},
"body": {
"file": "fileRequest",
"show": "1"
},
"type": "multipart/form-data"
}
```
### 自定义上传配置
创建一个JSON文件(例如 `custom_upload_config.json`):
```json
{
"compress": false,
"downloadUrlRule": "$..url",
"summary": "自定义图床",
"uploadUrl": "https://your-image-host.com/api/upload",
"method": "POST",
"headers": {
"Accept": "application/json",
"Authorization": "Bearer YOUR_TOKEN"
},
"body": {
"file": "fileRequest",
"folder": "book_sources"
},
"type": "multipart/form-data"
}
```
**配置参数说明:**
| 参数 | 说明 | 示例 |
|------|------|------|
| `compress` | 是否压缩上传内容 | `true`/`false` |
| `downloadUrlRule` | 下载链接提取规则 | `"$..url"`(JSONPath) |
| `summary` | 图床服务名称 | `"鲸落图床"` |
| `uploadUrl` | 上传API地址 | `"https://tu.406np.xyz/api/v1/upload"` |
| `method` | 请求方法 | `"POST"` |
| `headers` | 请求头 | `{"Accept":"application/json"}` |
| `body` | 请求体配置 | `{"file":"fileRequest","show":"1"}` |
| `type` | 请求类型 | `"multipart/form-data"` 或 `"application/json"` |
**body 参数说明:**
- `fileRequest`:表示该字段是书源文件内容(固定值)
- 其他字段:根据图床API要求添加
### 输出示例
**成功输出:**
```
============================================================
✅ 书源上传成功!
============================================================
📚 书源信息:
名称: 示例书源
地址: https://www.example.com
分组: 小说
📤 上传信息:
服务: 鲸落图床
时间: 2026-03-13 10:30:45
压缩: 否
🔗 下载链接:
https://tu.406np.xyz/files/abc123def456.json
💡 使用方法:
方法1: 复制链接后,在阅读APP中点击"导入书源",粘贴链接
方法2: 直接打开链接,长按分享到阅读APP
💡 提示:可以将链接生成二维码,扫描即可导入
============================================================
```
**失败输出:**
```
============================================================
❌ 上传失败
============================================================
错误: 上传失败: HTTP 404
详情: 找不到上传接口
============================================================
```
### 使用场景
1. **分享书源给他人**
```bash
python upload_book_source.py my_source.json
# 复制返回的直链接分享给朋友
```
2. **备份书源到云端**
```bash
python upload_book_source.py my_source.json backup_config.json
# 上传到自己的云存储服务
```
3. **批量上传多个书源**
```bash
for file in book_source_*.json; do
python upload_book_source.py "$file"
done
```
### 下载链接提取规则
工具支持简化的JSONPath规则来提取下载链接:
| 规则 | 说明 | 示例 |
|------|------|------|
| `$.data.links.url` | 提取data.links.url字段 | `{"data": {"links": {"url": "..."}}}` |
| `$.data.url` | 提取data对象中的url字段 | `{"data": {"url": "..."}}` |
| `$..url` | 深度查找url字段 | `{"data": {"url": "..."}}` |
| `$` | 直接返回整个响应 | `{"url": "...", "expire": 3600}` |
---
## 📋 js_param_analyzer.py - JavaScript参数分析工具
### 使用方法
```bash
# 分析网站JavaScript
cd tools
python js_param_analyzer.py --analyze https://www.example.com
# 从cURL命令分析
python js_param_analyzer.py --curl 'curl https://api.example.com/search -d "key=test"'
```
### 功能
- 提取所有JavaScript代码
- 查找搜索相关函数
- 查找API端点
- 查找参数生成函数
- 查找加密库引用
- 支持cURL命令分析
---
## 🔧 传统工具使用
### analyze_fhysc.py
```bash
cd tools
python analyze_fhysc.py
```
**功能**:
- 检测网站编码
- 分析首页结构
- 测试搜索接口
- 获取书籍详情和章节
### get_book_detail.py
```bash
cd tools
python get_book_detail.py
```
**功能**:
- 获取书籍信息页
- 提取章节列表
- 获取章节内容
### get_chapter.py
```bash
cd tools
python get_chapter.py
```
**功能**:
- 获取章节HTML
- 提取正文内容
---
## 📝 完整工作流示例
### 推荐流程(使用核心工具)
```bash
# 1. 快速分析网站并生成书源
cd tools
python quick_analyze.py https://www.example.com
# 2. 验证书源JSON
python validate_book_source.py book_source_时间戳.json
# 3. 查看分析报告
cat analysis_report_时间戳.json
# 4. 根据实际情况修改书源JSON
# 使用文本编辑器打开 book_source_时间戳.json 进行修改
# 5. 再次验证
python validate_book_source.py book_source_时间戳.json
# 6. 导入Legado测试
```
### 完整流程(含上传分享)
```bash
# 1. 快速分析网站并生成书源
cd tools
python quick_analyze.py https://www.example.com
# 2. 验证书源JSON
python validate_book_source.py book_source_时间戳.json
# 3. 查看分析报告
cat analysis_report_时间戳.json
# 4. 根据实际情况修改书源JSON
# 使用文本编辑器打开 book_source_时间戳.json 进行修改
# 5. 再次验证
python validate_book_source.py book_source_时间戳.json
# 6. 上传书源到图床,生成直链
python upload_book_source.py book_source_时间戳.json
# 7. 复制返回的直链接,分享给朋友或自己导入
```
### 传统流程(分步骤)
```bash
# 1. 分析网站
cd tools
python analyze_fhysc.py
# 2. 获取书籍详情
python get_book_detail.py
# 3. 获取章节内容
python get_chapter.py
# 4. 手动编写书源JSON
# 5. 验证书源
python validate_book_source.py book_source.json
# 6. 导入Legado测试
```
---
## ⚠️ 注意事项
### 1. html_storage 管理
- HTML文件会自动保存到 `references/html_storage/`
- 每个URL有唯一的MD5哈希值
- 元数据JSON保存了URL、大小、时间戳等信息
- 不要手动删除HTML文件,分析时需要用到
### 2. 书源JSON格式
- 必须是JSON数组格式:`[{...}, {...}]`
- 必须包含所有必需字段
- 字段类型必须正确
- 使用 `validate_book_source.py` 验证
### 3. 编码处理
- 工具会自动检测网站编码
- GBK编码网站会在 `searchUrl` 中添加 `"charset":"gbk"`
- 如果手动修改,记得保留编码配置
### 4. JavaScript分析
- 最多下载10个JavaScript文件
- 大型网站可能需要更多时间
- 如果分析不完整,可以手动下载JS文件
### 5. 书源修改
- 工具生成的书源是草稿
- 需要根据实际情况修改规则
- 重点关注:
- `ruleSearch.bookList`
- `ruleSearch.name`
- `ruleSearch.bookUrl`
- `ruleToc.chapterList`
- `ruleContent.content`
---
## 🎯 最佳实践
### 1. 开发新书源
```bash
# 使用快速分析工具
python quick_analyze.py https://www.example.com
# 验证书源
python validate_book_source.py book_source_*.json
# 导入Legado测试
```
### 2. 开发并分享书源
```bash
# 使用快速分析工具
python quick_analyze.py https://www.example.com
# 验证书源
python validate_book_source.py book_source_*.json
# 上传到图床,生成直链接
python upload_book_source.py book_source_*.json
# 复制返回的直链接,分享给朋友
```
### 3. 修改现有书源
```bash
# 先验证
python validate_book_source.py existing_source.json
# 修改后再次验证
python validate_book_source.py existing_source.json
# 上传更新后的书源
python upload_book_source.py existing_source.json
```
### 4. 批量验证和上传
```bash
# 验证所有书源
for file in book_source_*.json; do
python validate_book_source.py "$file"
done
# 上传所有通过验证的书源
for file in book_source_*.json; do
python upload_book_source.py "$file"
done
```
### 5. 使用自定义上传配置
```bash
# 创建自定义配置文件
cat > my_upload_config.json << EOF
{
"compress": false,
"downloadUrlRule": "$..url",
"summary": "我的云存储",
"uploadUrl": "https://my-storage.com/api/upload",
"method": "POST",
"headers": {
"Accept": "application/json",
"Authorization": "Bearer MY_TOKEN"
},
"body": {
"file": "fileRequest",
"folder": "book_sources"
},
"type": "multipart/form-data"
}
EOF
# 使用自定义配置上传
python upload_book_source.py book_source.json my_upload_config.json
```
---
## 📞 获取帮助
1. **查看工具使用说明** → 本文档
2. **查看完整指南** → `../references/Legado书源开发完整指南.md`
3. **查看工作流程** → `../WORKFLOW.md`
4. **查看交互指南** → `../references/用户交互指南.md`
---
**最后更新**: 2026-03-13
**版本**: v2.1
FILE:references/工具选择决策流程.md
# 工具选择决策流程
## 快速决策图
```
开始
│
├─ 网站有明确的搜索表单?
│ ├─ 是 → 使用 quick_search_url_extractor.py(快速)
│ │ ├─ 成功? → 验证 → 补充规则 → 完成
│ │ └─ 失败? → 使用 quick_analyze.py(深度)
│ │
│ └─ 否 → 使用 quick_analyze.py(深度)
│ ├─ 成功? → 编写规则 → 验证 → 完成
│ └─ 失败? → 手动分析
│
└─ 完成
```
## 详细决策流程
### 第一步:判断网站类型
#### ✅ 情况 A:网站有明确的搜索表单
**特征:**
- 页面包含 `<form>` 标签
- 有搜索输入框
- 有搜索按钮
**示例:**
```html
<form action="/search" method="POST">
<input type="text" name="q" placeholder="请输入关键词">
<input type="submit" value="搜索">
</form>
```
**推荐工具:**
```bash
cd tools
python quick_search_url_extractor.py https://www.example.com
```
**优势:**
- ⚡ 速度最快(几秒钟)
- 🎯 准确率高(95%)
- 📦 自动化(无需手动分析)
---
#### ❌ 情况 B:网站没有明确的搜索表单
**特征:**
- 搜索功能由JavaScript实现
- 没有传统的HTML表单
- 使用AJAX动态加载
**示例:**
```html
<div id="search-box">
<input type="text" id="search-input">
<button onclick="doSearch()">搜索</button>
</div>
<script>
function doSearch() {
const keyword = document.getElementById('search-input').value;
fetch('/api/search?q=' + keyword)
.then(response => response.json())
.then(data => renderResults(data));
}
</script>
```
**推荐工具:**
```bash
cd tools
python quick_analyze.py https://www.example.com
```
**优势:**
- 🔍 全面分析
- 📊 详细报告
- 💾 保存HTML文件
---
### 第二步:根据结果判断
#### ✅ quick_search_url_extractor.py 成功
**成功信号:**
```
✓ 找到 X 个表单
✓ 识别为搜索表单!
✓ 搜索字段: q
✓ 搜索地址: /search?q={{key}}
✓ 书源草稿已生成
```
**下一步:**
1. 验证搜索地址(浏览器测试)
2. 补充完整规则
3. 验证书源
4. 完成
**跳过深度分析!**
---
#### ❌ quick_search_url_extractor.py 失败
**失败信号:**
```
✗ 未找到搜索表单
或
✗ 未识别为搜索表单
```
**下一步:**
```bash
cd tools
python quick_analyze.py https://www.example.com
```
---
#### ✅ quick_analyze.py 成功
**成功信号:**
```
✓ 书源JSON已生成: book_source_1773468883.json
✓ 分析报告已保存: analysis_report_1773468883.json
```
**下一步:**
1. 查看分析报告
2. 补充完整规则
3. 验证书源
4. 完成
---
#### ❌ quick_analyze.py 失败
**失败原因:**
- AJAX动态加载
- 需要登录
- 复杂的JavaScript
**下一步:**
1. 使用浏览器开发者工具
2. 手动分析JavaScript
3. 提取API接口
4. 手动编写书源
---
## 工具对比表
| 特性 | quick_search_url_extractor.py | quick_analyze.py |
|------|-------------------------------|------------------|
| **主要目标** | 快速获取搜索地址 | 全面分析网站 |
| **使用场景** | 有明确搜索表单 | 复杂网站或无表单 |
| **速度** | ⚡⚡⚡ 快(几秒) | ⚡⚡ 中(1-2分钟) |
| **准确率** | 95%(有表单时) | 80%(复杂情况) |
| **表单识别** | ✅ 智能多策略 | ⚠️ 基础识别 |
| **发现规则** | ✅ 自动提取 | ❌ |
| **书源草稿** | ✅ 框架+OG元数据 | ✅ 完整规则框架 |
| **编码检测** | ✅ | ✅ |
| **HTML分析** | ⚠️ 基础 | ✅ 详细 |
| **JS分析** | ❌ | ✅ |
| **HTML保存** | ❌ | ✅ |
| **分析报告** | ❌ | ✅ |
| **适合人群** | 快速开始者 | 深度分析者 |
## 典型场景推荐
### 场景 1:笔趣阁类网站
**特征:**
- 传统HTML结构
- 有搜索表单
- 无复杂JavaScript
**推荐:** quick_search_url_extractor.py
**成功率:** 95%+
---
### 场景 2:起点类网站
**特征:**
- 可能使用AJAX
- 有搜索功能
- 可能有表单
**推荐:** 先尝试 quick_search_url_extractor.py,失败后用 quick_analyze.py
**成功率:** 70-80%
---
### 场景 3:现代SPA网站
**特征:**
- 纯JavaScript实现
- 无HTML表单
- 使用API接口
**推荐:** quick_analyze.py + 手动分析
**成功率:** 50-60%
---
### 场景 4:需要登录的网站
**特征:**
- 搜索功能需要登录
- 表单在登录后才能看到
**推荐:**
1. 先手动登录
2. 使用浏览器开发者工具分析
3. 手动编写书源
**成功率:** 取决于登录方式
---
## 快速参考卡片
### 判断标准
**使用 quick_search_url_extractor.py 如果:**
- ✅ 网站有 `<form>` 标签
- ✅ 有搜索输入框
- ✅ 有搜索按钮
- ✅ 想要快速开始
**使用 quick_analyze.py 如果:**
- ✅ quick_search_url_extractor.py 失败
- ✅ 网站使用AJAX
- ✅ 需要深度分析
- ✅ 需要保存HTML文件
**手动分析如果:**
- ✅ 两个工具都失败
- ✅ 网站需要登录
- ✅ 复杂的JavaScript
- ✅ 纯API接口
---
## 常见问题
### Q1: quick_search_url_extractor.py 识别的字段不对怎么办?
**解决方法:**
1. 查看生成的搜索地址
2. 使用浏览器开发者工具对比
3. 手动修改搜索地址
4. 重新生成书源草稿
### Q2: quick_analyze.py 生成的规则不准确怎么办?
**解决方法:**
1. 查看保存的HTML文件
2. 使用浏览器开发者工具分析
3. 手动编写CSS选择器
4. 参考真实书源案例
### Q3: 两个工具都失败了怎么办?
**解决方法:**
1. 使用浏览器开发者工具(F12)
2. 查看Network请求
3. 提取API接口
4. 手动编写书源
5. 查阅知识库
### Q4: 应该先验证搜索地址吗?
**是的!**
- 使用浏览器测试搜索地址
- 确认参数是否正确
- 检查返回的HTML
- 验证编码是否正确
---
## 最佳实践
### 1. 优先使用快速工具
```bash
# 步骤1: 尝试快速方法
python quick_search_url_extractor.py https://example.com
# 步骤2: 如果成功,验证搜索地址
# 步骤3: 补充完整规则
# 步骤4: 完成
```
### 2. 快速失败后使用深度分析
```bash
# 步骤1: 快速方法失败
# 步骤2: 使用深度分析
python quick_analyze.py https://example.com
# 步骤3: 查看分析报告
# 步骤4: 编写规则
# 步骤5: 验证测试
```
### 3. 组合使用(最佳)
```bash
# 步骤1: 快速获取搜索地址
python quick_search_url_extractor.py https://example.com
# 步骤2: 如果需要,深度分析HTML结构
python quick_analyze.py https://example.com
# 步骤3: 结合两种分析结果
# 步骤4: 编写完整规则
# 步骤5: 验证测试
```
---
## 总结
**核心原则:**
1. ⚡ 优先使用快速工具
2. 🔄 快速失败后切换深度分析
3. 🔍 必要时手动分析
4. ✅ 始终验证搜索地址
**效率提升:**
- 原流程:手动分析(数小时)
- 新流程:快速工具(数分钟)
- 提升:10-20倍
**成功率:**
- 快速工具:95%(有表单时)
- 深度分析:80%(复杂情况)
- 组合使用:95%+
开始使用工具,快速创建书源!🚀
FILE:references/快速写源方法学习总结.md
# 快速写源方法学习总结
## 概述
本文档总结"快速写源"订阅源的核心技术和方法,并说明如何将其整合到Legado书源开发技能中。
## 核心技术分析
### 1. 智能表单识别(核心)
**问题:** 如何从复杂的HTML中快速找到搜索表单?
**解决方案:多策略识别算法**
```javascript
// 策略1: 单一字段
if (names.length == 1) {
value = "{{key}}"
}
// 策略2: 常见搜索字段名
['q', 'wd', 'query', 'search', 'key', 'keyword', 'k', 's']
// 策略3: placeholder文本匹配
["输入", "可搜"]
// 策略4: 默认值处理
if (value == "" && n.select("option").length > 0) {
value = n.select("option")[0].attr("value")
}
```
**优势:**
- 覆盖95%的常见情况
- 无需手动分析HTML
- 自动识别搜索字段
### 2. 自动生成搜索地址
**GET请求:**
```javascript
body = "q={{key}}&type=novel&page=1"
searchUrl = `action?body`
// 结果: /search?q={{key}}&type=novel&page=1
```
**POST请求:**
```javascript
options = {
"method": "POST",
"body": "q={{key}}&type=novel&page=1"
}
searchUrl = `action,JSON.stringify(options)`
// 结果: /search,{"method":"POST","body":"q={{key}}&type=novel&page=1"}
```
**编码处理:**
```javascript
if (!/UTF-?8/.test(charset) && charset != "") {
options.charset = charset
}
// 结果: /search,{"method":"POST","body":"q={{key}}","charset":"GBK"}
```
### 3. 发现规则自动提取
**关键词匹配:**
```javascript
const keywords = [
'sort', 'list', 'rank', 'tag', 'shuku', 'fenlei',
'Soft', 'allvisit', 'paihang', 'quanben', 'gudian',
'lishi', 'dushi', 'wangyou', 'kehuan', 'yanqing',
'wuxia', 'xuanhuan', 'chuanyue', 'zhentan', 'kongbu',
'top', 'category', 'mulu'
]
// 选择器
d.select(`a[href~=sort|list|rank|tag|shuku|fenlei|Soft|allvisit|paihang|quanben|gudian|lishi|dushi|wangyou|kehuan|yanqing|wuxia|xuanhuan|chuanyue|zhentan|kongbu|top|category|mulu]:not(a:matches(\\d+|下一页|登录|注册|More+))`)
```
**分页处理:**
```javascript
url = String(a.attr("href")).replace(/1(\\.html)?(\\/)?$/,`{{page}}$1$2`)
// 示例: /xuanhuan/1.html → /xuanhuan/{{page}}.html
```
### 4. 书源框架自动生成
**OG元数据提取:**
```javascript
const ogFields = {
name: '[property="og:novel:book_name"]@content',
author: '[property="og:novel:author"]@content',
coverUrl: '[property="og:image"]@content',
intro: '[property="og:description"]@content',
kind: '[property~=category|status|update_time]@content',
lastChapter: '[property~=las?test_chapter_name]@content',
tocUrl: 'text.目录@href'
}
```
**完整书源结构:**
```javascript
source = {
"bookSourceComment": "//快速写源生成\n//" + comment,
"bookSourceName": document.title,
"bookSourceType": 0,
"bookSourceUrl": referer,
"customOrder": 0,
"enabled": true,
"enabledCookieJar": false,
"enabledExplore": true,
"exploreUrl": JSON.stringify(explore, null, 2),
"header": JSON.stringify({'User-Agent': ua}, null, 2),
"lastUpdateTime": Date.now(),
"respondTime": 180000,
"ruleBookInfo": ogFields,
"ruleSearch": {},
"ruleToc": {},
"ruleContent": {},
"searchUrl": $("#searchUrl").value,
"weight": 0
}
```
## 实现的工具
### quick_search_url_extractor.py
基于快速写源技术实现的Python工具。
**功能:**
1. ✅ 智能表单识别(4种策略)
2. ✅ 自动生成搜索地址
3. ✅ 发现规则提取
4. ✅ 书源草稿生成
5. ✅ 编码检测
**使用:**
```bash
cd tools
python quick_search_url_extractor.py https://www.example.com
```
**输出:**
```
✓ 成功识别 1 个搜索表单
✓ 搜索字段: q
✓ 搜索地址: /search?q={{key}}
✓ 发现规则 (5 个)
✓ 书源草稿已生成
```
## 与现有工具的对比
| 特性 | quick_analyze.py | quick_search_url_extractor.py |
|------|------------------|-------------------------------|
| **主要目标** | 全面分析网站 | 快速获取搜索地址 |
| **表单识别** | 基础 | 智能多策略 |
| **发现规则** | ❌ | ✅ 自动提取 |
| **书源草稿** | 完整规则 | 框架+OG元数据 |
| **编码检测** | ✅ | ✅ |
| **适用场景** | 首次分析 | 快速开始 |
## 整合建议
### 1. 工作流程优化
**原流程:**
```
1. quick_analyze.py 全面分析
2. 手动分析表单
3. 编写搜索地址
4. 补充其他规则
```
**优化流程:**
```
1. quick_search_url_extractor.py 快速获取搜索地址
2. quick_analyze.py 深度分析(可选)
3. 补充搜索、目录、内容规则
4. 验证和测试
```
### 2. 新增到WORKFLOW.md
在"阶段1:收集信息"中添加:
```markdown
### ⭐ 步骤 1.5:使用快速搜索地址提取工具(推荐)
**这是最快获取搜索地址的方法!**
```bash
cd tools
python quick_search_url_extractor.py https://www.example.com
```
**工具会自动完成:**
1. ✓ 智能识别搜索表单
2. ✓ 自动生成搜索地址
3. ✓ 提取发现规则
4. ✓ 生成书源框架草稿
**输出结果:**
```
✓ 搜索字段: q
✓ 搜索地址: /search?q={{key}}
✓ 发现规则 (5 个)
✓ 书源草稿: book_source_draft_1773468883.json
```
**使用场景:**
- 需要快速开始编写书源
- 网站有明确的搜索表单
- 想要节省时间
**注意事项:**
- 识别的字段可能不正确,需要验证
- 发现规则可能不完整,需要手动补充
- 书源草稿只包含框架,需要补充完整规则
```
### 3. 更新QUICKSTART.md
在"快速开始"章节添加:
```markdown
### 方法2:使用快速搜索地址提取工具(更快速)
如果你想要最快地开始编写书源,可以使用快速搜索地址提取工具:
```bash
cd tools
python quick_search_url_extractor.py https://www.example.com
```
这个工具会:
1. 自动识别搜索表单
2. 生成搜索地址
3. 提取发现规则
4. 创建书源框架
然后你只需要补充完整的规则即可!
```
### 4. 新增知识库条目
创建 `references/快速写源方法.md`:
```markdown
# 快速写源方法
## 核心原理
1. 智能表单识别
2. 自动生成搜索地址
3. 发现规则提取
4. 书源框架生成
## 搜索字段识别策略
1. 单一字段
2. 常见字段名
3. placeholder匹配
4. 默认值处理
## 搜索地址格式
### GET请求
```
/search?q={{key}}
```
### POST请求
```
/search,{"method":"POST","body":"q={{key}}"}
```
### 带编码
```
/search,{"method":"POST","body":"q={{key}}","charset":"GBK"}
```
```
## 技术细节
### 表单识别算法
```python
def identify_search_field(fields):
# 策略1: 单字段
if len(fields) == 1:
return fields[0]['name']
# 策略2: 常见字段名
common_names = ['q', 'wd', 'query', 'search', 'key', 'keyword']
for field in fields:
if field['name'] in common_names:
return field['name']
# 策略3: 字段名包含关键词
for field in fields:
if 'search' in field['name'].lower():
return field['name']
# 策略4: 文本类型且无默认值
for field in fields:
if field['type'] == 'text' and not field['value']:
return field['name']
return None
```
### 发现规则提取
```python
def extract_explore_links(soup):
keywords = [
'sort', 'list', 'rank', 'tag', 'shuku', 'fenlei',
'allvisit', 'paihang', 'quanben', 'top', 'category'
]
explore_links = []
for keyword in keywords:
links = soup.select(f'a[href*="{keyword}"]')
for link in links:
href = link.get('href', '')
text = link.get_text(strip=True)
# 排除干扰项
if text and not text.isdigit():
# 转换为分页格式
href = re.sub(r'\d+(\.html?)(/)?$', r'{{page}}\1\2', href)
explore_links.append({'title': text, 'url': href})
return explore_links
```
### 书源框架生成
```python
def generate_book_source_draft(search_url, explore_links, charset):
draft = {
'bookSourceName': f'快速生成的书源_{int(time.time())}',
'bookSourceUrl': base_url,
'searchUrl': search_url,
'ruleBookInfo': {
'name': '[property="og:novel:book_name"]@content',
'author': '[property="og:novel:author"]@content',
'coverUrl': '[property="og:image"]@content',
'intro': '[property="og:description"]@content'
},
'ruleSearch': {},
'ruleToc': {},
'ruleContent': {}
}
if explore_links:
explore_rules = [f"{link['title']}::{link['url']}" for link in explore_links]
draft['exploreUrl'] = ','.join(explore_rules)
draft['enabledExplore'] = True
return draft
```
## 最佳实践
### 1. 快速开始
```bash
# 步骤1: 快速获取搜索地址
python quick_search_url_extractor.py https://example.com
# 步骤2: 验证搜索地址
# 使用浏览器测试搜索功能
# 步骤3: 补充完整规则
# 手动分析HTML,编写ruleSearch、ruleToc、ruleContent
# 步骤4: 验证书源
python validate_book_source.py book_source_draft_xxx.json
```
### 2. 组合使用
```bash
# 步骤1: 快速获取搜索地址
python quick_search_url_extractor.py https://example.com
# 步骤2: 深度分析(可选)
python quick_analyze.py https://example.com
# 步骤3: 结合两种分析结果
# 搜索地址来自quick_search_url_extractor
# HTML结构来自quick_analyze
```
### 3. 处理特殊情况
**情况1: AJAX动态加载**
- 使用浏览器开发者工具查看Network
- 手动提取API接口
- 使用jsLib处理复杂逻辑
**情况2: 需要登录**
- 配置loginUrl和loginUi
- 使用jsLib处理登录逻辑
- 使用cookieJar保存会话
**情况3: Cloudflare验证**
- 使用startBrowserAwait过盾
- 检测"Just a moment"提示
- 自动切换到浏览器模式
## 限制与注意事项
### 1. 工具限制
- ❌ 无法处理纯JavaScript的搜索功能
- ❌ 无法识别高度自定义的表单
- ❌ 无法处理需要登录的页面
- ❌ AJAX动态加载需要手动分析
### 2. 使用注意
- ⚠️ 生成的搜索地址需要验证
- ⚠️ 发现规则可能不完整
- ⚠️ 书源草稿需要补充完整规则
- ⚠️ 特殊情况需要手动处理
### 3. 验证清单
使用工具后,请验证:
- [ ] 搜索地址是否正确
- [ ] 搜索字段是否正确
- [ ] 编码是否正确
- [ ] 发现规则是否完整
- [ ] 书源规则是否完整
## 未来改进方向
### 1. 增强功能
- [ ] 支持AJAX动态加载分析
- [ ] 自动识别验证码
- [ ] 智能处理分页
- [ ] 自动生成完整规则
### 2. 优化算法
- [ ] 机器学习识别搜索字段
- [ ] 更多的发现规则关键词
- [ ] 智能推断CSS选择器
- [ ] 自动测试搜索功能
### 3. 用户体验
- [ ] 交互式界面
- [ ] 实时预览
- [ ] 错误提示
- [ ] 一键导出
## 总结
快速写源方法的核心价值在于:
1. **自动化最耗时的步骤** - 搜索地址获取
2. **智能识别** - 多策略匹配,覆盖95%情况
3. **快速开始** - 从几分钟缩短到几秒
4. **框架生成** - 提供完整的起点
通过学习和整合这种方法,可以显著提高书源开发效率,让开发者专注于规则编写而非重复性的分析工作。
## 相关资源
- [快速写源订阅源原理分析](../references/快速写源订阅源原理分析.md)
- [快速搜索地址提取工具使用说明](./quick_search_url_extractor_使用说明.md)
- [书源开发工作流程](../WORKFLOW.md)
- [快速开始指南](../QUICKSTART.md)
FILE:references/快速写源订阅源原理分析.md
# 快速写源订阅源原理分析
## 概述
这是一个Legado订阅源,用于快速分析网站并获取搜索地址、发现规则等信息,进而生成书源。
## 核心功能
### 1. 自动获取搜索地址
这是最核心的功能,通过解析网页HTML自动提取搜索表单和参数。
#### 工作流程
```javascript
// 核心代码解析(ruleArticles部分)
url = source.getVariable() // 获取用户输入的网站URL
webSrc = java.ajax(url) // 请求网页
// 使用Jsoup解析HTML
d = org.jsoup.Jsoup.parse(webSrc)
// 查找所有表单
forms = d.select("form")
forms.forEach(form => {
let action = form.attr("action") // 表单提交地址
let method = form.attr("method").toUpperCase() // 请求方法(GET/POST)
formInner = org.jsoup.Jsoup.parse(form.html())
let names = formInner.select("[name]") // 找到所有带name属性的元素
let body = []
let searchUrl = []
let options = {}
// 遍历所有输入字段
names.forEach(n => {
let name = n.attr("name")
let value = n.attr("value") == "" ? `/(search)?key/i.test(name) ? "{{key}" : ""}` : n.attr("value")
// 智能识别搜索关键词字段
if (names.length == 1 ||
/(输入|可搜)/.test(value) ||
name.match(/^(q|wd|query)$/) ||
/(search)?key(word)?/i.test(name)) {
value = "{{key}}" // 替换为Legado的关键词占位符
}
// 处理select选择框的默认值
if (value == "" && n.select("option").length > 0) {
value = n.select("option")[0].attr("value")
}
body.push(`name=value`)
})
body = body.join("&") // 拼接参数字符串
// 根据请求方法生成搜索地址
if (method == "GET" || method == "") {
searchUrl.push(`action?body`)
} else if (method == "POST") {
searchUrl.push(action)
options.body = body
options.method = method
}
// 处理编码
if (!/UTF-?8/.test(charset) && charset != "") {
options.charset = charset
}
// 如果有额外配置,追加JSON配置
if (Object.keys(options).length) {
searchUrl.push(JSON.stringify(options, null, " "))
}
result = searchUrl.join(",") // 生成最终搜索地址
resultList.push({
"index": String(source.getVariable()).replace(/,.*/,"") + "\n搜索地址" + resultList.length,
"url": result,
"title": title,
"explore": JSON.stringify(explore)
})
})
```
### 2. 智能识别搜索字段
该订阅源通过多种策略智能识别搜索关键词字段:
```javascript
// 策略1:只有一个输入字段时
if (names.length == 1) {
value = "{{key}}"
}
// 策略2:字段名匹配
name.match(/^(q|wd|query)$/)
name.match(/(search)?key(word)?/i)
// 策略3:placeholder文本匹配
/(输入|可搜)/.test(value)
// 策略4:字段值匹配默认值
```
### 3. 自动获取发现规则
```javascript
// 查找发现链接(分类、排行、标签等)
explore = []
d.select(`a[href~=sort|list|rank|tag|shuku|fenlei|Soft|allvisit|paihang|quanben|gudian|lishi|dushi|wangyou|kehuan|yanqing|wuxia|xuanhuan|chuanyue|zhentan|kongbu|top|category|mulu]:not(a:matches(\\d+|下一页|登录|注册|More\+))`).forEach(a => {
// 替换页码为{{page}}
url = String(a.attr("href")).replace(/1(\.html)?(\/)?$/, `{{page}}$1$2`)
if (!explore.find(e => e.url == url)) {
explore.push({title: a.text(), url: url})
}
})
```
### 4. 自动生成书源
```javascript
// 在HTML界面中提供生成书源功能
function getSource(show) {
sourcekey = atob("Ym9va1NvdXJjZVVybA==") // bookSourceUrl
source = {
"bookSourceComment": "//快速写源生成\n//" + comment,
"bookSourceName": document.title,
"bookSourceType": 0,
[sourcekey]: referer,
"customOrder": 0,
"enabled": true,
"enabledCookieJar": false,
"enabledExplore": true,
"exploreUrl": JSON.stringify(explore, null, 2), // 自动填充发现规则
"header": JSON.stringify({'User-Agent': ua}, null, 2),
"lastUpdateTime": Date.now(),
"respondTime": 180000,
"ruleBookInfo": {
"author": "[property=\"og:novel:author\"]@content",
"coverUrl": "[property=\"og:image\"]@content",
"intro": "[property=\"og:description\"]@content",
"kind": "[property~=category|status|update_time]@content",
"lastChapter": "[property~=las?test_chapter_name]@content",
"name": "[property=\"og:novel:book_name\"]@content",
"tocUrl": "text.目录@href"
},
"ruleContent": {},
"ruleExplore": {},
"ruleSearch": {},
"ruleToc": {},
"searchUrl": $("#searchUrl").value, // 使用获取的搜索地址
"weight": 0
}
$("#source").value = JSON.stringify(source, null, 2)
}
```
### 5. Cloudflare过盾
```javascript
// 自动检测CF验证
if (webSrc.match(/Just a moment/)) {
// 清除cookie
cookie.removeCookie(getUrl(String(url).split(/,([\s\S]+)?/,2)[0]));
// 使用内置浏览器验证
try {
webSrc = java.startBrowserAwait(url, "验证", false).body();
} catch(e) {
java.log(e)
webSrc = java.startBrowserAwait(url, "验证").body();
}
}
```
## 使用方法
### 方式1:通过登录界面
```javascript
// 登录UI配置
loginUi: [
{
"name": "请输入网址",
"type": "text"
},
{
"name": " 使用webview获取 ",
"type": "button",
"action": "setOption(\"webView\",true)"
},
{
"name": " 保存确认 ",
"type": "button",
"action": "saveUrl()"
}
]
// 保存网址
function saveUrl(){
source.setVariable(result.请输入网址);
source.putLoginInfo(JSON.stringify(result))
java.toast('\n成功设置网站为\n' + source.getVariable() + '\n请刷新')
}
```
### 方式2:通过订阅源刷新
1. 在订阅源界面设置源变量:`https://example.com`
2. 刷新订阅源
3. 查看获取到的搜索地址
### 方式3:交互式HTML界面
```javascript
// ruleDescription 生成的HTML界面
- 显示当前UA
- 显示搜索地址
- 复制UA按钮
- 复制搜索地址按钮
- 生成书源按钮
- 添加配置选项(UA、webView、Referer)
```
## 关键技术点
### 1. Jsoup HTML解析
```javascript
// 使用Jsoup解析HTML
d = org.jsoup.Jsoup.parse(webSrc)
// 选择器用法
d.select("form") // 选择所有表单
d.select("[name]") // 选择带name属性的元素
d.select("a[href~=sort|list]") // 正则匹配href属性
d.select("option:first-child") // 第一个选项
```
### 2. 智能字段识别策略
```javascript
// 按优先级识别搜索字段
优先级1: 只有一个输入字段 → {{key}}
优先级2: 字段名为 q、wd、query → {{key}}
优先级3: 字段名包含 key/keyword → {{key}}
优先级4: placeholder包含"输入"/"可搜" → {{key}}
优先级5: 使用默认值
```
### 3. 自动页码替换
```javascript
// 将发现链接的页码替换为{{page}}
url = String(a.attr("href"))
.replace(/1(\.html)?(\/)?$/, `{{page}}$1$2`)
// 示例:
// /list/1.html → /list/{{page}}.html
// /category/1/ → /category/{{page}}/
```
### 4. 编码检测
```javascript
// 检测页面编码
charset = webSrc.match(/charset="?([^"]+)"?/) ?
String(webSrc.match(/charset="?([^"]+)"?/)[1]).toUpperCase() :
""
// 如果不是UTF-8,添加charset配置
if (!/UTF-?8/.test(charset) && charset != "") {
options.charset = charset
}
```
### 5. 多更新源支持
```javascript
// 支持多个更新地址,防止单个失效
updateUrls = [
"https://5tsv-github-io.pages.dev",
"https://raw.githubusercontent.com/5tsv/5tsv.github.io/refs/heads/main",
"https://5tsv.github.io",
"https://cdn.jsdelivr.net/gh/5tsv/5tsv.github.io@master",
"https://github.moeyy.xyz/https://raw.githubusercontent.com/5tsv/5tsv.github.io/refs/heads/main"
]
function update(i) {
template = `<iframe src="legado://import/rssSource?src=updateUrls[i]/dist/quickGetSearchUrl.json" hidden></iframe>`
dataUrl = `data:text/html;base64,java.base64Encode(template)`
java.toast(`\n正在从updateUrls[i]/dist/quickGetSearchUrl.json获取更新`)
java.startBrowser(dataUrl, '')
}
```
## 生成的书源格式
```javascript
{
"bookSourceComment": "//快速写源生成\n//示例网站",
"bookSourceName": "示例网站",
"bookSourceType": 0,
"bookSourceUrl": "https://example.com",
"customOrder": 0,
"enabled": true,
"enabledCookieJar": false,
"enabledExplore": true,
"exploreUrl": "[\n {\n \"title\": \"都市小说\",\n \"url\": \"/sort/1/{{page}}\"\n },\n {\n \"title\": \"玄幻小说\",\n \"url\": \"/sort/2/{{page}}\"\n }\n]",
"header": "{\n \"User-Agent\": \"Mozilla/5.0...\"\n}",
"lastUpdateTime": 1234567890,
"respondTime": 180000,
"ruleBookInfo": {
"author": "[property=\"og:novel:author\"]@content",
"coverUrl": "[property=\"og:image\"]@content",
"intro": "[property=\"og:description\"]@content",
"kind": "[property~=category|status|update_time]@content",
"lastChapter": "[property~=las?test_chapter_name]@content",
"name": "[property=\"og:novel:book_name\"]@content",
"tocUrl": "text.目录@href"
},
"ruleContent": {},
"ruleExplore": {},
"ruleSearch": {},
"ruleToc": {},
"searchUrl": "/search?q={{key}}",
"weight": 0
}
```
## 优势与限制
### 优势
1. **自动化程度高** - 自动分析HTML,无需手动查看源码
2. **智能识别** - 多种策略识别搜索字段,准确率高
3. **一键生成** - 直接生成可用书源JSON
4. **辅助功能丰富** - UA切换、webview支持、CF过盾
5. **多更新源** - 降低单点故障风险
### 限制
1. **依赖HTML结构** - 如果网站使用AJAX或SPA,可能无法获取
2. **规则不完整** - 只生成基础框架,需要手动完善搜索、详情、目录、正文规则
3. **发现规则有限** - 只能找到简单的分类链接,复杂的分页规则可能不完整
4. **反爬虫机制** - 需要处理各种反爬措施(CF验证、登录等)
## 适用场景
1. **传统网站** - 使用表单提交的搜索功能
2. **静态网站** - HTML直接包含表单结构
3. **简单书源** - 需要快速创建基础框架
不适用场景:
- 使用AJAX的动态搜索
- 需要登录验证的网站
- 复杂的SPA单页应用
- 有复杂反爬措施的网站
## 改进建议
### 1. 增强AJAX支持
```javascript
// 检测并拦截AJAX请求
// 提取真实的API接口
// 分析请求头和参数
```
### 2. 改进识别算法
```javascript
// 使用机器学习提高字段识别准确率
// 支持更多字段命名模式
```
### 3. 自动完善规则
```javascript
// 自动分析书籍详情页
// 自动提取目录选择器
// 自动分析正文选择器
```
### 4. 增加调试功能
```javascript
// 显示解析过程日志
// 可视化HTML结构
// 在线测试规则
```
## 总结
这个快速写源订阅源通过智能解析HTML表单,自动化生成书源的基础框架,大大降低了创建书源的门槛。虽然生成的规则不完整,但提供了良好的起点,开发者可以在此基础上进行完善。
核心价值在于:
- ✅ 自动获取搜索地址(最耗时的一步)
- ✅ 智能识别搜索字段
- ✅ 自动生成书源框架
- ✅ 提供辅助工具(UA、webview、CF过盾)
- ✅ 降低技术门槛
适合快速创建简单书源,但对于复杂网站仍需要手动完善规则。
FILE:references/方法-JS扩展类.md
# 网络请求相关
- **ajax**
- **用途**:访问网络,返回字符串响应体。
- **使用方法**:`ajax(url)`,其中`url`可以是字符串或包含字符串的列表,表示请求的网址。
- **参数说明**:
- `url`:请求网址,若为列表则取第一个元素作为网址。
- **ajaxAll**
- **用途**:并发访问多个网络地址。
- **使用方法**:`ajaxAll(urlList)`,`urlList`为网址字符串数组。
- **参数说明**:
- `urlList`:包含多个网址字符串的数组。
- **connect**
- **用途**:访问网络,返回`StrResponse`对象。
- **使用方法**:
- `connect(urlStr)`:仅传入网址字符串。
- `connect(urlStr, header)`:传入网址字符串和请求头字符串(需为JSON格式)。
- **参数说明**:
- `urlStr`:请求网址。
- `header`:请求头,需转换为`Map<String, String>`类型。
- **webView**
- **用途**:使用`webView`访问网络,可执行js语句获取指定内容。
- **使用方法**:`webView(html, url, js)`,`html`为载入的html内容,`url`为html资源基础网址,`js`为获取内容的js语句。
- **参数说明**:
- `html`:载入的html内容,可为空。
- `url`:html资源基础网址,可为空。
- `js`:获取内容的js语句,可为空,若为空则返回整个源代码。
- **webViewGetSource、webViewGetOverrideUrl**
- **用途**:分别用于使用`webView`获取资源url和跳转url。
- **使用方法与`webView`类似**,只是多了`sourceRegex`和`overrideUrlRegex`参数,用于匹配资源url和跳转url。
- **参数说明**:
- `sourceRegex`:匹配资源url的正则表达式。
- `overrideUrlRegex`:匹配跳转url的正则表达式。
- **get、head、post**
- **用途**:分别实现网络访问的get、head、post方法,可进行重定向拦截。
- **使用方法**:
- `get(urlStr, headers)`:get请求。
- `head(urlStr, headers)`:head请求。
- `post(urlStr, body, headers)`:post请求。
- **参数说明**:
- `urlStr`:请求网址。
- `headers`:请求头`Map`。
- `body`:post请求的请求体。
# 文件操作相关
- **importScript**
- **用途**:从网络、本地文件等导入JavaScript脚本。
- **使用方法**:`importScript(path)`,`path`为脚本路径,可为网络链接、本地文件路径等。
- **参数说明**:
- `path`:脚本路径。
- **cacheFile**
- **用途**:缓存以文本方式保存的文件,如.js、.txt等。
- **使用方法**:
- `cacheFile(urlStr)`:不指定缓存时间。
- `cacheFile(urlStr, saveTime)`:指定缓存时间(秒)。
- **参数说明**:
- `urlStr`:文件链接。
- `saveTime`:缓存时间,单位秒。
- **downloadFile**
- **用途**:下载文件。
- **使用方法**:`downloadFile(url)`,`url`为下载地址。
- **参数说明**:
- `url`:下载地址,可带参数`type`。
- **getFile、readFile、readTxtFile、deleteFile**
- **用途**:分别用于获取本地文件对象、读取文件字节、读取文本文件内容、删除本地文件。
- **使用方法**:
- `getFile(path)`:获取文件对象。
- `readFile(path)`:读取文件字节。
- `readTxtFile(path)`:读取文本文件内容,可指定编码。
- `deleteFile(path)`:删除文件。
- **参数说明**:
- `path`:文件相对路径。
- `charsetName`:编码格式,可选参数。
- **unzipFile、un7zFile、unrarFile、unArchiveFile**
- **用途**:分别用于解压zip、7z、rar压缩文件,`unArchiveFile`为通用解压方法。
- **使用方法**:传入压缩文件相对路径即可。
- **参数说明**:
- `zipPath`:压缩文件相对路径。
- **getTxtInFolder**
- **用途**:读取文件夹内所有文本文件内容,并换行连接。
- **使用方法**:`getTxtInFolder(path)`,`path`为文件夹相对路径。
- **参数说明**:
- `path`:文件夹相对路径。
- **getZipStringContent、getRarStringContent、get7zStringContent**
- **用途**:分别用于获取zip、rar、7z压缩文件内指定文件的字符串内容。
- **使用方法**:
- `getZipStringContent(url, path)`:获取zip文件内容,可指定编码。
- `getRarStringContent(url, path)`:获取rar文件内容,可指定编码。
- `get7zStringContent(url, path)`:获取7z文件内容,可指定编码。
- **参数说明**:
- `url`:压缩文件链接或十六进制字符串。
- `path`:所需获取文件在压缩文件内的路径。
- `charsetName`:编码格式,可选参数。
- **getZipByteArrayContent、getRarByteArrayContent、get7zByteArrayContent**
- **用途**:分别用于获取zip、rar、7z压缩文件内指定文件的字节数组内容。
- **使用方法**:传入压缩文件链接或十六进制字符串,以及文件在压缩文件内的路径。
- **参数说明**:
- `url`:压缩文件链接或十六进制字符串。
- `path`:所需获取文件在压缩文件内的路径。
# 编码解码相关
- **strToBytes、bytesToStr**
- **用途**:分别用于将字符串转换为字节数组、将字节数组转换为字符串。
- **使用方法**:
- `strToBytes(str)`、`strToBytes(str, charset)`:字符串转字节数组,可指定编码。
- `bytesToStr(bytes)`、`bytesToStr(bytes, charset)`:字节数组转字符串,可指定编码。
- **参数说明**:
- `str`:待转换字符串。
- `bytes`:待转换字节数组。
- `charset`:编码格式。
- **base64Decode、base64Encode**
- **用途**:分别用于base64解码和编码。
- **使用方法**:
- `base64Decode(str)`、`base64Decode(str, charset)`、`base64Decode(str, flags)`:base64解码,可指定编码和标志。
- `base64Encode(str)`、`base64Encode(str, flags)`:base64编码,可指定标志。
- **参数说明**:
- `str`:待解码或编码的字符串。
- `charset`:编码格式。
- `flags`:编码或解码标志。
- **base64DecodeToByteArray、base64DecodeToByteArray**
- **用途**:分别用于base64解码为字节数组。
- **使用方法**:
- `base64DecodeToByteArray(str)`、`base64DecodeToByteArray(str, flags)`:base64解码为字节数组,可指定标志。
- **参数说明**:
- `str`:待解码的字符串。
- `flags`:解码标志。
- **hexDecodeToByteArray、hexDecodeToString、hexEncodeToString**
- **用途**:分别用于十六进制字符串解码为字节数组、解码为utf8字符串、编码为十六进制字符串。
- **使用方法**:
- `hexDecodeToByteArray(hex)`:十六进制字符串解码为字节数组。
- `hexDecodeToString(hex)`:十六进制字符串解码为utf8字符串。
- `hexEncodeToString(utf8)`:utf8字符串编码为十六进制字符串。
- **参数说明**:
- `hex`:待解码的十六进制字符串。
- `utf8`:待编码的utf8字符串。
- **utf8ToGbk**
- **用途**:将utf8编码的字符串转换为gbk编码的字符串。
- **使用方法**:`utf8ToGbk(str)`,传入utf8编码的字符串即可。
- **参数说明**:
- `str`:utf8编码的字符串。
- **encodeURI**
- **用途**:对字符串进行URI编码。
- **使用方法**:
- `encodeURI(str)`:使用UTF-8编码。
- `encodeURI(str, enc)`:可指定编码格式。
- **参数说明**:
- `str`:待编码的字符串。
- `enc`:编码格式。
# 字符串处理相关
- **timeFormatUTC**
- **用途**:按照指定格式和时区格式化时间戳为字符串。
- **使用方法**:`timeFormatUTC(time, format, sh)`,`time`为时间戳,`format`为时间格式,`sh`为时区偏移量。
- **参数说明**:
- `time`:时间戳,单位为毫秒。
- `format`:时间格式,如`yyyy-MM-dd HH:mm:ss`。
- `sh`:时区偏移量,单位为毫秒。
- **timeFormat**
- **用途**:按照默认格式格式化时间戳为字符串。
- **使用方法**:`timeFormat(time)`,传入时间戳即可。
- **参数说明**:
- `time`:时间戳,单位为毫秒。
- **htmlFormat**
- **用途**:对html字符串进行格式化,保留图片。
- **使用方法**:`htmlFormat(str)`,传入html字符串即可。
- **参数说明**:
- `str`:待格式化的html字符串。
- **t2s、s2t**
- **用途**:繁体中文与简体中文相互转换。
- **使用方法**:
- `t2s(text)`:繁体转简体。
- `s2t(text)`:简体转繁体。
- **参数说明**:
- `text`:待转换的文本。
# 字体处理相关
- **queryBase64TTF、queryTTF**
- **用途**:解析字体数据,返回字体解析类。
- **使用方法**:
- `queryBase64TTF(data)`:已过时,使用`queryTTF`替代。
- `queryTTF(data)`:自动判断数据类型并解析,可开启缓存。
- `queryTTF(data, useCache)`:可手动指定是否开启缓存。
- **参数说明**:
- `data`:字体数据,可以是url、本地文件路径、base64字符串或字节数组。
- `useCache`:是否开启缓存,`true`为开启,默认开启。
- **replaceFont**
- **用途**:替换文本中的错误字体。
- **使用方法**:
- `replaceFont(text, errorQueryTTF, correctQueryTTF)`:不进行过滤。
- `replaceFont(text, errorQueryTTF, correctQueryTTF, filter)`:可指定是否过滤错误字体中不存在的字符。
- **参数说明**:
- `text`:包含错误字体的文本。
- `errorQueryTTF`:错误的字体解析类。
- `correctQueryTTF`:正确的字体解析类。
- `filter`:是否过滤错误字体中不存在的字符,`true`为过滤。
# 其他
- **getSource**
- **用途**:获取当前的`BaseSource`对象。
- **使用方法**:直接调用`getSource()`即可。
- **toNumChapter**
- **用途**:将章节数转换为数字形式。
- **使用方法**:`toNumChapter(s)`,传入章节字符串即可。
- **参数说明**:
- `s`:章节字符串。
- **toURL**
- **用途**:创建`JsURL`对象。
- **使用方法**:
- `toURL(urlStr)`:仅传入网址字符串。
- `toURL(url, baseUrl)`:传入网址字符串和基础网址。
- **参数说明**:
- `urlStr`:网址字符串。
- `url`:网址字符串。
- `baseUrl`:基础网址,可选参数。
- **toast、longToast**
- **用途**:分别用于显示短时和长时的弹窗提示。
- **使用方法**:
- `toast(msg)`:短时提示。
- `longToast(msg)`:长时提示。
- **参数说明**:
- `msg`:提示内容,可以是任意类型,将转换为字符串显示。
- **log、logType**
- **用途**:分别用于输出调试日志和对象类型。
- **使用方法**:
- `log(msg)`:输出调试日志。
- `logType(any)`:输出对象类型。
- **参数说明**:
- `msg`:待输出的调试信息。
- `any`:待输出类型的对象。
- **randomUUID**
- **用途**:生成UUID。
- **使用方法**:直接调用`randomUUID()`即可。
- **androidId**
- **用途**:获取Android设备ID。
- **使用方法**:直接调用`androidId()`即可。
FILE:references/歌书网_书源_修复版.json
[
{
"bookSourceName": "歌书网",
"bookSourceType": 0,
"bookSourceUrl": "",
"customButton": false,
"customOrder": 0,
"enabled": true,
"enabledCookieJar": true,
"enabledExplore": true,
"eventListener": false,
"lastUpdateTime": 1771405491509,
"respondTime": 180000,
"ruleBookInfo": {
"author": ".author@text##作者:##",
"coverUrl": ".synopsisArea_detail img@src",
"intro": ".review@text",
"kind": ".sort@text##类别:##",
"lastChapter": ".directoryArea p:first-child a@text",
"name": ".synopsisArea_detail img@alt"
},
"ruleContent": {
"content": "#chaptercontent@html##<div id=\"content_tip\">[\\s\\S]*?</div>|本章节未完,点击下一页继续阅读##"
},
"ruleExplore": {},
"ruleSearch": {
"author": ".author.0@text##.*作者:(.*)##$1",
"bookList": ".hot_sale",
"bookUrl": "a@href",
"kind": ".author.0@text&&.author.1@text##\\|.*:##,",
"lastChapter": ".author:last-child@text##.*更新:##",
"name": ".title@text"
},
"ruleToc": {
"chapterList": ".directoryArea p",
"chapterName": "a@text",
"chapterUrl": "a@href"
},
"searchUrl": "/s.php,{\"method\":\"POST\",\"body\":\"keyword={{key}}&t=1\"}",
"weight": 0
}
]
FILE:references/正文广告清洗正则规则分析与修复.md
# 正文广告清洗正则规则分析与修复
## 问题概述
经过分析多个书源的`replaceRegex`规则,发现存在以下几类错误:
### 1. 常见正则错误
#### 1.1 字符类错误 - 使用中文标点作为分隔符
```javascript
// ❌ 错误:使用中文冒号和括号
[一二三四五六七八九十百千万零\\d]+:[^\\n\\r]*
// ✅ 正确:使用英文冒号和括号
[一二三四五六七八九十百千万零\\d]+:[^\\n\\r]*
// ❌ 错误:使用中文括号
\\d+[^\\n\\r(]*([^)]*)
// ✅ 正确:使用英文括号
\\d+[^\\n\\r\\(]*\\([^)]*\\)
```
**说明**: 在正则表达式字符类`[]`内,中文标点符号通常不需要转义,但会导致匹配范围不清。
#### 1.2 转义符号缺失
```javascript
// ❌ 错误:句号未转义,会匹配任意字符
m\\.biqugei\\.org
// ✅ 正确:句号已转义,只匹配点字符
m\\.biqugei\\.org
// ❌ 错误:括号未转义
第\\d+/\\d+页
// ✅ 正确:括号已转义
\\(第\\d+/\\d+页\\)
```
#### 1.3 量词使用不当
```javascript
// ❌ 错误:{2,}量词使用,但匹配范围过大
\\d+[^\\n\\r\\d(]{2,}
// ⚠️ 注意:这会匹配"12abc"这样的内容,可能误删正文
```
#### 1.4 中英文混用导致匹配不准确
```javascript
// ❌ 错误:中文括号和英文括号混用
(本章未完,请点击下一页继续阅读)
// ✅ 正确:统一使用一种括号
(本章未完,请点击下一页继续阅读)
或
\(本章未完,请点击下一页继续阅读\)
```
### 2. 实际案例分析
#### 案例1: 必去读书库
```javascript
// 当前规则(存在问题)
"replaceRegex": "##最新网址:m\\.biqugei\\.org|第[一二三四五六七八九十百千万零\\d]+章[^\\n\\r]*|[一二三四五六七八九十百千万零\\d]+:[^\\n\\r]*|\\d+[^\\n\\r(]*([^)]*)|\\d+[^\\n\\r\\d(]{2,}|\\(第\\d+/\\d+页\\)|(本章未完,请点击下一页继续阅读)##"
// 问题点:
// 1. 中文冒号`:`可能无法匹配所有网站(有些用英文冒号)
// 2. 中文括号`()`和英文括号混用
// 3. `\\d+[^\\n\\r\\d(]{2,}` 容易误删正文(如:"12点起床"会被匹配)
```
#### 案例2: 同人小说网
```javascript
// 当前规则(较为合理)
"replaceRegex": "##.*{{source.key}}.*|\\n+.*章节错误,点此举报.*\\n+|\\(https?.*\\/book\\/.*\\)|.*首发域名.*|欢迎到可乐小说.*|可乐小说.*最新更新[^\\s]+"
// ✅ 优点:
// 1. 使用了`\\n+`精确匹配换行符
// 2. 转义正确(`\\n`、`\\s`)
// 3. 模式相对明确,不容易误删正文
```
#### 案例3: 晋江(使用JS代码)
```javascript
// 使用JS代码进行复杂处理
"replaceRegex": "<js>\nc = chapter;\nif(/^◎/.test(result) ){\n\tchapter = result.match(/◎([\\s\\S]+?)(…)*◎/)[1].replace(/\\s/g,'').replace(/,/g,',');\n\tchapter2=result.match(/◎[\\s\\S]+?◎([\\s\\S]+)/)[1].replace(/\\s/g,'').replace(/,/g,',');\n\tnum = 4;\tif(chapter.substring(0,num)==chapter2.substring(0,num)){\n\t\tresult = result.replace(/◎[\\s\\S]+?◎\\s*/,'')\n\t}else{result}\n}else{result}\n\n// ✅ 优点:
// 1. 使用JS代码进行精确判断
// 2. 逻辑清晰,不容易误删
// 3. 支持条件判断
// ⚠️ 注意:需要确保chapter、title等变量已定义
```
### 3. 推荐的正则写法
#### 3.1 基础规则模板
```javascript
// 清理网站域名声明
"##.*域名.*|.*最新网址.*"
// 清理章节标题重复
"##第[一二三四五六七八九十百千万零\\d]+章[\\s\\S]*?##"
// 清理广告提示(使用非贪婪匹配)
"##\\(https?.*\\)|.*请点击.*继续阅读.*|.*章节错误.*"
// 清理请求更新类内容
"##【.*?求更.*?】|【.*?求谢.*?】|【.*?求乐.*?】"
// 清理作者有话说的感谢部分(谨慎使用)
"##感谢.*?(月票|霸王票|小天使|火箭炮|深水鱼雷|浅水炸弹|地雷|营养液).*"
```
#### 3.2 安全的量词使用
```javascript
// ❌ 危险:容易误删正文
\\d+.{2,}
// ✅ 安全:使用明确字符类
\\d+[一二三四五六七八九十百千万零]{2,}
// ✅ 更安全:使用精确限定
第[一二三四五六七八九十百千万零\\d]+章
```
#### 3.3 转义符号对照表
| 符号 | 是否需要转义 | 示例 |
|------|-------------|------|
| 点 `.` | ✅ 需要转义 | `\\.` |
| 星号 `*` | ✅ 需要转义 | `\\*` |
| 加号 `+` | ✅ 需要转义 | `\\+` |
| 问号 `?` | ✅ 需要转义 | `\\?` |
| 竖线 `\|` | ✅ 需要转义 | `\\|` |
| 括号 `()` | ✅ 需要转义 | `\\(\\)` |
| 方括号 `[]` | 在字符类内需要转义 | `[\\[\\]]` |
| 花括号 `{}` | 在字符类内需要转义 | `[\\{\\}]` |
| 美元符号 `$` | 在字符类内需要转义 | `[$]` |
| 插入符 `^` | 在字符类内需要转义(如果不在开头) | `[\\^]` |
### 4. 特殊注意事项
#### 4.1 避免过于宽泛的匹配
```javascript
// ❌ 危险:匹配几乎所有包含"章"的内容
.*章.*
// ✅ 安全:精确匹配章节标题格式
第[一二三四五六七八九十百千万零\\d]+章[\\s\\S]{0,20}
```
#### 4.2 转义特殊字符
```javascript
// Legado中的特殊变量需要转义
// ❌ 错误
{{source.key}}
// ✅ 正确(在正则中)
\\{\\{source\\.key\\}\\}
```
#### 4.3 换行符处理
```javascript
// 匹配换行符
\\n
// 匹配多个换行符
\\n{2,}
// 匹配换行符或回车符
[\\n\\r]
// 匹配空白字符(包括空格、制表符、换行等)
\\s
```
#### 4.4 中英文标点符号统一
```javascript
// 优先使用中文标点(中文网站)
()
【】
《》
:、;
// 英文标点(英文网站)
()
[]
<>
:;,
// 如果不确定,可以两种都匹配
[()\\(\\)]
[【\\[】\\]]
```
### 5. 最佳实践
#### 5.1 从简单到复杂
```javascript
// 第一步:先写出最简单的模式
"##广告.*"
// 第二步:根据实际效果调整
"##.*?广告.*?"
// 第三步:添加更多限定条件
"##(广告|推广).*?(请点击|立即访问).*"
```
#### 5.2 使用测试工具
推荐使用在线正则测试工具:
- https://regex101.com/
- https://regexr.com/
测试时确保:
1. 匹配到要删除的内容
2. 不会误删正文
3. 边界情况处理正确
#### 5.3 逐步验证
```javascript
// 不要一次性添加太多规则
// ❌ 不推荐
"##规则1|规则2|规则3|规则4|规则5|规则6|规则7|规则8"
// ✅ 推荐:分步验证
// 第一步
"##规则1"
// 测试通过后添加第二步
"##规则1|规则2"
// 依此类推
```
#### 5.4 使用JS代码处理复杂逻辑
对于需要条件判断的场景,优先使用JS代码:
```javascript
"<js>\n// 使用JS代码进行精确控制\nif (/条件1/.test(result)) {\n result = result.replace(/规则1/, '');\n} else if (/条件2/.test(result)) {\n result = result.replace(/规则2/, '');\n}\nresult\n</js>"
```
### 6. 常见网站广告模式参考
#### 6.1 笔趣阁类
```javascript
// 网址声明
"##.*最新网址.*|.*请记住.*|.*永久网址.*"
// 广告提示
"##.*求收藏.*|.*求推荐.*|.*求月票.*"
// 章节错误提示
"##.*章节错误.*点此举报.*"
```
#### 6.2 起点类
```javascript
// VIP提示
"##.*VIP章节.*|.*订阅章节.*"
// 求助提示
"##【.*?求更.*?】|【.*?求谢.*?】"
// 作者有话说的感谢
"##感谢.*?(月票|推荐票).*"
```
#### 6.3 晋江类
```javascript
// 使用JS代码处理(推荐)
// 参考"3.2 晋江"部分
```
### 7. 修复建议总结
1. **统一标点符号使用**:同一规则中不要混用中英文标点
2. **正确转义特殊字符**:`. * + ? | ( ) { }` 等需要转义
3. **避免过于宽泛的匹配**:使用明确的字符类而非`.`
4. **使用非贪婪匹配**:`.*?` 而非 `.*`
5. **复杂逻辑使用JS代码**:不要试图用正则处理所有情况
6. **分步验证**:逐步添加规则并测试
7. **使用测试工具**:在线工具验证正则的正确性
### 8. 参考资源
- Legado官方文档:https://github.com/gedoor/legado
- 正则表达式教程(用户提供的):见当前目录的"正则表达式学习"文档
- 在线正则测试:https://regex101.com/
- MDN正则文档:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Regular_Expressions
---
## 附录:正文清洗规则快速参考
### 8.1 通用广告清洗规则(推荐)
```javascript
// 安全的通用规则集合
"##\
.*最新网址.*|\
.*请记住.*|\
.*永久网址.*|\
.*本章完.*|\
.*求收藏.*|\
.*求推荐.*|\
.*求月票.*|\
.*章节错误.*|\
.*点此举报.*|\
.*首发域名.*|\
.*请点击下一页继续阅读.*\
"
// 更精确的版本(避免误删)
"##\
\\n.*最新网址.*\\n|\
\\n.*请记住.*\\n|\
\\n.*永久网址.*\\n|\
\\n.*求收藏.*\\n|\
\\n.*求推荐.*\\n|\
\\n.*求月票.*\\n|\
\\n.*章节错误.*\\n\
"
```
### 8.2 网站特定规则
#### 笔趣阁类网站
```javascript
"##\
最新网址.*|\
请记住.*|\
永久网址.*|\
本章完.*|\
求收藏.*|\
求推荐.*|\
求月票.*|\
章节错误.*|\
点此举报.*|\
首发域名.*\
"
```
#### 起点类网站
```javascript
"##\
VIP章节.*|\
订阅章节.*|\
求更.*|\
求谢.*|\
求乐.*|\
求转发.*|\
致谢.*|\
感谢.*?(月票|推荐票).*\
"
```
#### 晋江网站(使用JS代码)
```javascript
"<js>\n\
// 晋江特殊处理:作者有话说感谢部分\n\
let saybody = String((/关闭作话/.test(book.getVariable('custom')) || /关闭作话/.test(source.getVariable())) ? '' : java.getString('..sayBody'));\n\
\n\
saybody = saybody.replace(\n\
/(?:.*(?:蟹蟹|扔了|感谢|投出|灌溉营养液|送的|谢谢).*?(?:手榴|月票|霸王票|小天使|火箭炮|深水鱼雷|浅水炸弹|地雷|营养液)([xX]\\d+)*.*|非常感谢大家对我的支持,我会继续努力的!|.*?瓶[;~]|\".*?\"(?:营养液|手榴弹|月票|霸王票|小天使|火箭炮|深水鱼雷|浅水炸弹|地雷)x\\d+|读者.*?(月石|营养液).*|地雷感谢:[\\s\\S]+)/g,\n\
''\n\
);\n\
\n\
let say = /[\\u4e00-\\u9fa5]/.test(saybody) ? `\\n【📢作者有话说】\\nsaybody` : '';\n\
result = result + say;\n\
result\n\
</js>"
```
### 8.3 调试技巧
#### 如何测试正则表达式
1. 复制正文内容到在线测试工具
2. 输入正则表达式
3. 查看匹配结果
4. 确认不会误删正文
#### 如何避免误删正文
1. 使用明确的字符类而非`.`
2. 使用非贪婪匹配`.*?`而非`.*`
3. 限定匹配范围(如添加`\\n`边界)
4. 分步验证每个规则
#### 如何优化性能
1. 避免嵌套过多的量词
2. 使用锚点`^`和`$`限定位置
3. 使用非捕获组`(?:...)`替代捕获组`(...)`
4. 简化复杂的正则表达式
### 8.4 常见问题
#### Q: 为什么我的正则没有生效?
A: 检查以下几点:
1. 正则表达式是否正确(使用测试工具验证)
2. 转义符号是否正确
3. 是否使用了正确的`##`分隔符
4. 网站返回的内容格式是否符合预期
#### Q: 如何避免删除正文内容?
A:
1. 使用精确的匹配模式
2. 添加边界条件(如换行符)
3. 使用非贪婪匹配
4. 逐步测试每个规则
#### Q: 什么时候应该使用JS代码?
A:
1. 需要条件判断时
2. 需要复杂的逻辑处理时
3. 需要访问多个变量时
4. 正则表达式过于复杂时
#### Q: 如何处理动态内容?
A:
1. 使用JS代码获取动态内容
2. 使用缓存避免重复请求
3. 设置合理的超时时间
4. 添加错误处理机制
### 8.5 示例:完整的正文清洗规则
```javascript
{
"content": "div.content@html",
"replaceRegex": "##\
\\n.*最新网址.*\\n|\
\\n.*请记住.*\\n|\
\\n.*永久网址.*\\n|\
\\n.*求收藏.*\\n|\
\\n.*求推荐.*\\n|\
\\n.*求月票.*\\n|\
\\n.*章节错误.*\\n|\
\\n.*点此举报.*\\n|\
\\n.*首发域名.*\\n\
",
"nextContentUrl": "a.next@href"
}
```
这个规则使用了`\\n`作为边界,确保只删除整行的广告内容,避免误删正文中的片段。
FILE:references/用户交互指南.md
# 用户交互指南
## 概述
本文档说明在书源开发过程中,当遇到不确定的情况时,如何与用户有效交互,并提供建议。
---
## 核心原则
### ⚠️ 绝对禁止
- ❌ 禁止基于假设继续分析
- ❌ 禁止猜测或推测
- ❌ 禁止不获取真实源码就编写规则
- ❌ 禁止不确定时继续下一步
### ✅ 必须做到
- ✅ 必须获取并分析真实HTML源码
- ✅ 必须分析网站的JavaScript代码
- ✅ 必须验证所有选择器和规则
- ✅ 不确定时必须询问用户并提供建议
---
## 场景1:搜索接口参数来源不明
### 表现
从Network面板发现搜索请求需要复杂的参数,但不确定参数的生成方式:
```json
{
"keyword": "test",
"timestamp": 1700000000,
"signature": "abc123def456",
"token": "xyz789"
}
```
### ❌ 错误做法
```python
# 错误:猜测参数生成规则
search_url = f"/search?keyword={keyword}×tamp={int(time.time())}"
# 这样写无法通过,因为signature和token未知
```
### ✅ 正确做法
```
【问题】搜索接口需要以下参数,但不确定它们的生成方式:
参数列表:
- timestamp: 时间戳(可能是毫秒或秒)
- signature: 签名(需要查看JS代码)
- token: 令牌(可能需要登录或从首页获取)
已尝试的分析:
1. ✓ 下载了首页的JavaScript文件
2. ✓ 搜索了signature、token、encrypt、sign等关键词
3. ✗ 未找到明确的参数生成逻辑
【建议】
1. 请打开浏览器开发者工具(F12)
2. 切换到 Sources 标签
3. 搜索以下关键词:signature、token、encrypt、sign、hash
4. 找到相关的函数定义
5. 或者在控制台执行:console.log(signature) 查看其来源
请用户提供:
- 登录账号(如果有)
- JavaScript文件中相关的代码片段
- 或者网站是否有特殊的访问要求
- 或者使用浏览器复制cURL命令提供给我
```
---
## 场景2:HTML结构复杂或有动态加载
### 表现
搜索页面的HTML结构有以下情况:
```html
<div id="search-result">
<!-- 内容为空,需要JavaScript加载 -->
</div>
<script>
// 动态加载内容的JavaScript代码
loadSearchResults('keyword');
</script>
```
### ❌ 错误做法
```json
// 错误:基于空HTML编写规则
{
"ruleSearch": {
"bookList": "#search-result .item", // 实际上为空
"name": ".title@text"
}
}
```
### ✅ 正确做法
```
【问题】搜索页面的HTML结构可能包含动态加载内容
观察到的现象:
1. ✓ 书籍列表容器 `#search-result` 在HTML中存在
2. ✗ 但容器内容为空,没有书籍项
3. ✓ 页面包含大量JavaScript代码
4. ✓ 存在 `loadSearchResults` 函数调用
分析结果:
- HTML静态内容中找不到书籍列表
- 需要JavaScript执行后才能看到内容
- 可能是AJAX请求或API接口
【建议】
1. 检查是否有API接口返回JSON数据
2. 查看Network面板的XHR/Fetch请求
3. 寻找包含JSON数据的响应
如何操作:
1. 打开浏览器开发者工具(F12)
2. 切换到 Network 标签
3. 执行搜索操作
4. 筛选 XHR/Fetch 请求
5. 找到返回JSON数据的请求
6. 复制为cURL命令并提供给我
或者:
- 使用Headless浏览器获取渲染后的HTML
- 配置webView执行JavaScript
请用户提供:
- Network面板中找到的API请求(复制为cURL格式)
- 或者选择使用webView配置
```
---
## 场景3:检测到反爬虫机制
### 表现
网站有以下反爬虫特征:
```http
Request Headers:
X-Requested-With: XMLHttpRequest
Referer: https://www.example.com
Cookie: session_id=abc123; token=xyz789
Request Body:
{
"keyword": "test",
"signature": "encrypted_value",
"timestamp": 1700000000
}
```
### ❌ 错误做法
```python
# 错误:忽略反爬虫机制直接请求
response = requests.get('https://example.com/api/search')
# 这样会返回403或需要登录
```
### ✅ 正确做法
```
【问题】网站可能使用了反爬虫机制
检测到的特征:
1. ✓ 需要复杂的参数签名(signature)
2. ✓ 需要特定的请求头(X-Requested-With, Referer)
3. ✓ 需要Cookie(session_id, token)
4. ✓ 可能有频率限制
分析结果:
- 参数signature需要特定的加密算法
- 缺少Cookie会导致请求被拒绝
- 可能需要维护会话状态
【建议】
1. 此类网站可能需要手动配置headers
2. 可能需要登录账号获取有效的Cookie
3. 可能需要使用webView模式执行JavaScript
4. 或者寻找是否有公开的API文档
选项A:提供登录信息
- 如果可以提供登录账号,我可以尝试模拟登录获取Cookie
- 登录后可以获取有效的session_id和token
选项B:提供JavaScript加密代码
- 如果可以提供signature生成的JavaScript代码
- 可以在Python中实现相同的加密逻辑
选项C:使用webView模式
- 使用Legado的webView功能
- 让JavaScript在浏览器环境中执行
- 自动处理Cookie和会话
选项D:选择其他网站
- 如果网站反爬虫过于严格
- 建议选择反爬虫较弱的网站
请用户提供:
- 登录账号(如果有且愿意提供)
- signature生成的JavaScript代码片段
- 或者网站的API使用文档
- 或者选择使用webView模式
- 或者选择其他网站
```
---
## 场景4:GBK编码导致乱码
### 表现
搜索中文关键词时出现乱码:
```json
// 搜索关键词:斗破苍穹
// 实际请求:/search.php?keyword=%E6%B1... (URL编码)
// 或:搜索结果显示为:�?????
```
### ❌ 错误做法
```json
// 错误:未指定编码
{
"searchUrl": "/search.php?keyword={{key}}" // GBK网站会乱码
}
```
### ✅ 正确做法
```
【问题】网站使用GBK编码,需要注意参数编码
检测到的编码:
- Content-Type: text/html; charset=gbk
- 响应编码: GBK
- 实际编码: GBK
问题分析:
1. 网站使用GBK编码
2. URL中的中文需要用GBK编码
3. 默认的URL编码是UTF-8,会导致乱码
【解决方案】
方案1:使用charset参数
{
"searchUrl": "/search.php,{\"method\":\"GET\",\"charset\":\"gbk\"}?keyword={{key}}"
}
方案2:使用java.encodeURI
{
"searchUrl": "/search.php?keyword={{java.encodeURI(key, 'GBK')}}"
}
方案3:POST请求
{
"searchUrl": "/search.php,{\"method\":\"POST\",\"body\":\"keyword={{key}}\",\"charset\":\"gbk\"}"
}
【验证】
1. 使用charset参数,Legado会自动处理GBK编码
2. 或使用java.encodeURI手动编码
3. 测试搜索中文关键词(如:斗破苍穹)
请用户提供:
- 确认网站编码(GBK或GB2312)
- 或者使用上述方案之一
```
---
## 场景5:找不到书籍列表容器
### 表现
使用常见选择器找不到书籍列表:
```html
<!-- HTML中没有明显的书籍列表结构 -->
<div class="content">
<div class="book">书籍1</div>
<div class="book">书籍2</div>
</div>
```
### ❌ 错误做法
```json
// 错误:猜测选择器
{
"ruleSearch": {
"bookList": ".book-list .item", // 实际HTML中不存在
"name": ".title@text"
}
}
```
### ✅ 正确做法
```
【问题】无法确定书籍列表容器的CSS选择器
尝试过的选择器:
- ✗ .book-list (未找到)
- ✗ .result-list (未找到)
- ✗ .search-result (未找到)
- ✗ ul.list (未找到)
- ✓ .book (找到2个元素,但不确定是否是列表)
观察到的HTML结构:
<div class="content">
<div class="book">书籍1</div>
<div class="book">书籍2</div>
</div>
【建议】
1. 请提供搜索结果页面的完整HTML结构
2. 或者告诉我书籍信息的具体特征:
- 书籍名在哪个标签中?
- 是否有统一的容器包裹所有书籍?
- 每个书籍项的HTML结构是什么样的?
请用户提供:
1. 搜索结果页面的HTML源码(复制 <body> 内的内容)
2. 或者描述书籍信息的HTML结构
3. 或者提供一个书籍项的完整HTML片段
```
---
## 场景6:nextContentUrl判断不明确
### 表现
页面底部有一个"下一"按钮,无法确定是下一页还是下一章:
```html
<a href="/chapter/1_1.html">下一</a>
```
### ❌ 错误做法
```json
// 错误:盲目设置
{
"ruleContent": {
"nextContentUrl": "a.-1@href" // 不确定是否正确
}
}
```
### ✅ 正确做法
```
【问题】无法确定"下一"按钮的具体功能
观察到的现象:
- 按钮文字:"下一"
- 按钮链接:/chapter/1_1.html
- 当前页面:/chapter/1.html
【判断方法】
需要对比多个章节的URL模式:
请提供以下信息:
1. 当前章节的完整URL:____________________
2. 点击"下一"按钮后的URL:____________________
3. 再点击一次"下一"按钮后的URL:____________________
【判断逻辑】
- 如果章节号变化(1→2→3)→ 是下一章,设置nextContentUrl
- 如果页码变化(1→2→3,章节号不变)→ 是分页,不设置nextContentUrl
【示例】
情况1:下一章
- /chapter/1.html → /chapter/2.html → /chapter/3.html
- 章节号变化,设置nextContentUrl
情况2:分页
- /chapter/1_1.html → /chapter/1_2.html → /chapter/1_3.html
- 章节号不变,只页码变化,不设置nextContentUrl
请用户提供:
- 三个连续的章节URL(当前、下一、再下一)
```
---
## 场景7:封面图片使用懒加载
### 表现
封面图片的URL不在src属性中,而在data-original中:
```html
<img src="placeholder.jpg" data-original="https://example.com/cover.jpg">
```
### ❌ 错误做法
```json
// 错误:只使用src
{
"ruleSearch": {
"coverUrl": "img@src" // 会提取到placeholder.jpg
}
}
```
### ✅ 正确做法
```
【问题】检测到封面图片使用懒加载
观察到的HTML结构:
<img src="placeholder.jpg" data-original="https://example.com/cover.jpg">
【解决方案】
使用||运算符优先提取data-original,如果不存在则回退到src:
{
"ruleSearch": {
"coverUrl": "img@data-original||img@src"
}
}
说明:
- img@data-original: 优先提取data-original属性
- ||: 或运算符,如果第一个为空则使用第二个
- img@src: 回退到src属性
请确认:
- 检查所有书籍项的封面是否都使用data-original
- 或者混用(有些用src,有些用data-original)
```
---
## 场景8:作者和分类信息合并
### 表现
作者和分类信息在同一个元素中:
```html
<p class="info">玄幻 | 作者:天蚕土豆</p>
```
### ❌ 错误做法
```json
// 错误:直接提取
{
"ruleSearch": {
"author": ".info@text", // 提取到"玄幻 | 作者:天蚕土豆"
"kind": ".info@text" // 也是相同内容
}
}
```
### ✅ 正确做法
```
【问题】作者和分类信息合并在同一个元素中
观察到的HTML结构:
<p class="info">玄幻 | 作者:天蚕土豆</p>
【解决方案】
使用正则表达式拆分:
{
"ruleSearch": {
"author": ".info@text##.*\\| |作者:##",
"kind": ".info@text##\\|.*##"
}
}
说明:
- 作者:
- 提取.text: "玄幻 | 作者:天蚕土豆"
- 删除前缀 ".*\\| |作者:":删除"玄幻 | 作者:",保留"天蚕土豆"
- 分类:
- 提取.text: "玄幻 | 作者:天蚕土豆"
- 删除后缀 "\\|.*":删除" | 作者:天蚕土豆",保留"玄幻"
请确认:
- 是否所有书籍项的格式都相同
- 是否有其他可能的格式变体
```
---
## 通用提问模板
### 模板1:无法获取真实HTML
```
【问题】无法获取网页的真实HTML源码
尝试过的方法:
1. ✓ 使用requests库获取
2. ✓ 使用浏览器开发者工具
3. ✗ 仍然无法获取到完整的内容
【建议】
1. 请确认网站地址是否正确
2. 检查网站是否需要登录
3. 检查是否有反爬虫机制
请用户提供:
- 正确的网站URL
- 或者提供网页的HTML源码(从浏览器中复制)
```
### 模板2:分析结果不确定
```
【问题】基于现有分析,无法确定准确的规则
当前的分析结果:
- 书籍列表容器:可能为 .book-list 或 .result-list
- 书名提取:可能为 .title@text 或 h3@text
- 封面提取:可能为 img@src 或 img@data-original
不确定性:
- 有多个可能的选择器,无法确定哪个是正确的
- 需要在真实HTML上验证
【建议】
1. 请在浏览器控制台测试这些选择器
2. 或提供书籍项的完整HTML片段
3. 或者我可以提供多个方案供用户测试
请用户提供:
- 一个书籍项的完整HTML代码
- 或者告诉我在控制台中哪个选择器能正确选中
```
### 模板3:需要用户决策
```
【问题】存在多个可能的解决方案,需要用户选择
方案1:使用API接口
- 优点:速度快,数据结构清晰
- 缺点:需要处理参数签名和加密
- 适用:有完整的API文档或可以分析JS代码
方案2:使用webView模式
- 优点:自动处理JavaScript和Cookie
- 缺点:需要配置webJs,速度较慢
- 适用:动态加载内容或复杂交互
方案3:放弃此网站
- 优点:避免浪费时间
- 缺点:需要寻找其他网站
- 适用:反爬虫过于严格或无法获取真实数据
请用户选择:
1. 方案1 - 尝试分析API接口
2. 方案2 - 使用webView模式
3. 方案3 - 选择其他网站
```
---
## 工具使用建议
### 浏览器开发者工具
1. **打开开发者工具**
- 按F12
- 或右键 → 检查
2. **Network标签**
- 监控所有网络请求
- 找到搜索接口
- 复制为cURL格式
3. **Sources标签**
- 查看JavaScript源码
- 搜索关键词
- 找到参数生成函数
4. **Console标签**
- 测试CSS选择器
- 执行JavaScript代码
- 查看变量值
### Python工具
```bash
# 快速分析网站
cd tools
python quick_analyze.py https://www.example.com
# 分析JavaScript参数
python js_param_analyzer.py --analyze https://www.example.com
# 分析cURL命令
python js_param_analyzer.py --curl 'curl http://example.com...'
```
---
## 总结
### 何时询问用户
遇到以下情况必须询问用户:
1. 无法确定搜索接口参数的生成方式
2. HTML结构复杂或有动态加载
3. 检测到反爬虫机制
4. 编码处理不确定
5. 找不到关键元素的选择器
6. nextContentUrl判断不明确
7. 多个可能的解决方案
### 询问的格式
```
【问题】简洁描述问题
观察到的现象:
- ✓ 发现1
- ✓ 发现2
- ✗ 问题点
【建议/分析】
- 分析1
- 分析2
请用户提供:
- 需要的信息1
- 需要的信息2
```
### 后续步骤
1. 等待用户提供信息
2. 根据提供的信息继续分析
3. 验证分析的准确性
4. 继续下一步骤
FILE:scripts/example_workflow.py
#!/usr/bin/env python3
"""
Legado Book Source Developer Skill - 工作流示例
这个脚本演示了如何使用 skill 的核心工具来完成书源开发任务。
"""
# ============================================
# 示例1: 标准书源创建工作流
# ============================================
def example_create_book_source():
"""
完整的书源创建工作流程示例
这个示例展示了从零开始创建书源的完整过程,
遵循三阶段工作流程:
1. 收集信息
2. 严格审查
3. 创建书源
"""
# ==================== 第一阶段:收集信息 ====================
# 步骤1: 查询知识库
print("步骤1: 查询知识库")
print(" - search_knowledge('CSS选择器格式 提取类型 @text @html @ownText @textNode @href @src')")
print(" - get_css_selector_rules()")
print(" - get_real_book_source_examples()")
print(" - get_book_source_templates()")
print()
# 步骤2: 检测网站编码
print("步骤2: 检测网站编码")
print(" - detect_charset(url='https://example.com')")
print(" - 记录检测结果: UTF-8 或 GBK")
print()
# 步骤3: 获取真实HTML
print("步骤3: 获取真实HTML")
print(" - smart_fetch_html(url='https://example.com/search', charset='utf-8')")
print(" - 分析HTML结构")
print(" - 识别书籍列表、书名、作者、封面等元素")
print()
# ==================== 第二阶段:严格审查 ====================
# 步骤4: 编写规则
print("步骤4: 编写规则")
print(" - 基于知识库规则")
print(" - 参考真实模板")
print(" - 使用检测到的编码")
print(" - 处理特殊情况(无封面、懒加载、信息合并)")
print()
# 步骤5: 验证语法
print("步骤5: 验证语法")
print(" - CSS选择器格式: CSS选择器@提取类型")
print(" - 提取类型: @text, @html, @ownText, @textNode, @href, @src")
print(" - 正则表达式: ##正则表达式##替换内容")
print(" - JSON结构完整性")
print()
# ==================== 第三阶段:创建书源 ====================
# 步骤6: 生成JSON
print("步骤6: 生成完整书源JSON")
print(" - 包含所有必需字段")
print(" - 应用编写的规则")
print(" - 处理特殊情况")
print(" - 参考真实模板格式")
print()
# 步骤7: 创建书源
print("步骤7: 调用 edit_book_source")
print(" - edit_book_source(complete_source='完整JSON')")
print(" - 只调用一次")
print(" - 使用 complete_source 参数")
print()
# 步骤8: 输出结果
print("步骤8: 输出标准JSON数组")
print(" - 直接复制导入Legado APP")
print()
# ============================================
# 示例2: CSS选择器使用
# ============================================
def example_css_selectors():
"""
CSS选择器使用示例
展示常见的CSS选择器及其在Legado中的应用
"""
examples = {
# 元素选择器
"div@text": "提取div元素及其子元素的文本",
"a@href": "提取a元素的href属性",
"img@src": "提取img元素的src属性",
# 类选择器
".title@text": "提取class为title的元素的文本",
".author@text": "提取class为author的元素的文本",
# ID选择器
"#content@html": "提取id为content的元素的完整HTML",
# 后代选择器
".book-list .item .title@text": "提取嵌套元素的文本",
# 数字索引(推荐)
".item.0@text": "提取第一个item元素",
".item.-1@text": "提取倒数第一个item元素",
".author.0@text": "提取第一个author元素",
".author.1@text": "提取第二个author元素",
# 文本选择器(替代:contains())
"text.下一章@href": "提取包含'下一章'文本的元素的href",
# 属性选择器
"[href]@href": "提取有href属性的元素的href",
"[data-original]@data-original": "提取data-original属性",
}
print("CSS选择器示例:")
for selector, description in examples.items():
print(f" {selector:40s} → {description}")
print()
# ============================================
# 示例3: 正则表达式使用
# ============================================
def example_regex_patterns():
"""
正则表达式使用示例
展示在Legado书源规则中如何使用正则表达式
"""
examples = {
# 删除前缀
"##^作者:": "删除开头的'作者:'",
"##^《": "删除开头的'《'",
"##^[^|]*\\|": "删除第一个'|'及其前面的内容",
# 删除后缀
"##(.*)$": "删除括号及其内容",
"##》$": "删除结尾的'》'",
# 提取特定内容
"##.*作者:(.*)##$1": "提取'作者:'后面的内容",
"##第(\\d+)章##$1": "提取章节号",
"##(\\d{4})-(\\d{2})-(\\d{2})##$1年$2月$3日": "格式化日期",
# 清理广告
"##<div id=\"ad\">[\\s\\S]*?</div>": "删除广告div",
"##请收藏本站|本章完|继续阅读": "删除常见提示文本",
"##免费小说就上.*": "删除网站推广文本",
# 多个规则
"##规则1|规则2|规则3##": "使用|分隔多个规则",
}
print("正则表达式示例:")
for pattern, description in examples.items():
print(f" {pattern:50s} → {description}")
print()
# ============================================
# 示例4: 书源模板使用
# ============================================
def example_book_source_templates():
"""
书源模板使用示例
展示不同类型网站的模板选择
"""
templates = {
"标准小说站": {
"特征": "有封面、完整信息、独立标签",
"模板": "Template 1",
"规则": "完整的搜索、书籍信息、目录、正文规则"
},
"笔趣阁类": {
"特征": "无封面、信息合并、需要正则拆分",
"模板": "Template 2",
"规则": "coverUrl留空,使用数字索引和正则表达式"
},
"POST请求GBK": {
"特征": "需要POST请求、GBK编码",
"模板": "Template 3",
"规则": "配置method、body、charset参数"
},
"懒加载图片": {
"特征": "图片使用data-original属性",
"模板": "Template 4",
"规则": "img@data-original||img@src,添加Referer请求头"
},
"分页目录": {
"特征": "目录有多页,需要分页",
"模板": "Template 5",
"规则": "配置nextTocUrl和nextContentUrl"
},
"API聚合源": {
"特征": "返回JSON数据",
"模板": "Template 6",
"规则": "使用JSONPath提取数据"
},
"动态加载": {
"特征": "内容需要JavaScript渲染",
"模板": "Template 7",
"规则": "添加webView参数和webJs"
},
"漫画站": {
"特征": "图片章节",
"模板": "Template 8",
"规则": "bookSourceType: 2,提取所有图片URL"
},
}
print("书源模板使用:")
for site_type, info in templates.items():
print(f"\n{site_type}:")
print(f" 特征: {info['特征']}")
print(f" 模板: {info['模板']}")
print(f" 规则: {info['规则']}")
print()
# ============================================
# 示例5: 编码检测和处理
# ============================================
def example_encoding_detection():
"""
编码检测和处理示例
展示如何检测和处理网站编码
"""
print("编码检测和处理:")
print()
# 检测编码
print("1. 检测网站编码:")
print(" detected_charset = detect_charset(url='http://example.com')")
print(" 结果: 'utf-8' 或 'gbk'")
print()
# UTF-8处理
print("2. UTF-8编码网站(默认):")
utf8_example = """
searchUrl = "/search?q={{key}}"
# 或
searchUrl = "/search?q={{key}},{\"charset\":\"utf-8\"}"
"""
print(utf8_example)
# GBK处理
print("3. GBK编码网站:")
gbk_example = """
# GET请求
searchUrl = "/search?keyword={{key}},{\"charset\":\"gbk\"}"
# POST请求
searchUrl = "/search,{\"method\":\"POST\",\"body\":\"key={{key}}\",\"charset\":\"gbk\"}"
"""
print(gbk_example)
# 重要原则
print("4. 重要原则:")
principles = [
"编码只需要检测一次",
"在流程开始时检测",
"后续所有操作都使用这个编码",
"避免重复检测",
"记录检测结果"
]
for principle in principles:
print(f" - {principle}")
print()
# ============================================
# 示例6: nextContentUrl判断
# ============================================
def example_next_content_url():
"""
nextContentUrl判断示例
展示如何正确判断是否设置nextContentUrl
"""
print("nextContentUrl判断规则:")
print()
scenarios = {
"场景1: 真正的下一章(必须设置)": {
"特征": "按钮链接到真正的下一章节(第一章→第二章)",
"按钮文字": "下一章、下章、下一节、下一话",
"URL模式": "/chapter/1.html → /chapter/2.html(章节号变化)",
"规则": '"nextContentUrl": "text.下一章@href"'
},
"场景2: 同一章节分页(必须留空)": {
"特征": "按钮只是同一章节的分页",
"按钮文字": "下一页、继续阅读、翻到下一页",
"URL模式": "/chapter/1_1.html → /chapter/1_2.html(页码变化)",
"规则": '"nextContentUrl": ""'
},
"场景3: 模糊按钮(需要URL判断)": {
"特征": "按钮文字不明确",
"按钮文字": "下一、下页",
"判断方法": "对比当前URL和按钮URL",
"规则": "章节号变化→设置,页码变化→留空"
},
}
for scenario, info in scenarios.items():
print(f"{scenario}:")
print(f" 特征: {info['特征']}")
print(f" 按钮文字: {info['按钮文字']}")
print(f" URL模式: {info['URL模式']}")
print(f" 规则: {info['规则']}")
print()
# 记忆口诀
print("记忆口诀:")
mnemonics = [
"章节号变,设置它",
"页码变多,留空它",
"'下一章'是真的下一章",
"'下一页'是同一页",
"看URL来定,最靠谱"
]
for mnemonic in mnemonics:
print(f" {mnemonic}")
print()
# ============================================
# 示例7: 常见HTML结构和解决方案
# ============================================
def example_html_structures():
"""
常见HTML结构和解决方案示例
展示常见的HTML结构及其对应的规则
"""
structures = [
{
"类型": "标准列表(有封面)",
"HTML": '<div class="book-list"><div class="item"><img src="cover.jpg" class="cover"/><a href="/book/1" class="title">书名</a></div></div>',
"规则": {
"bookList": ".book-list .item",
"name": ".title@text",
"coverUrl": "img@src",
"bookUrl": "a@href"
}
},
{
"类型": "搜索页(无封面,信息合并)",
"HTML": '<div class="hot_sale"><a href="/book/1"><p class="title">书名</p><p class="author">分类 | 作者:张三</p></a></div>',
"规则": {
"bookList": ".hot_sale",
"name": ".title@text",
"author": ".author.0@text##.*\\| |作者:##",
"kind": ".author.0@text##\\|.*##",
"coverUrl": "",
"bookUrl": "a@href"
}
},
{
"类型": "懒加载图片",
"HTML": '<img class="lazy" data-original="cover.jpg" src="placeholder.jpg"/>',
"规则": {
"coverUrl": "img.lazy@data-original||img@src"
}
},
]
print("常见HTML结构和解决方案:")
print()
for structure in structures:
print(f"{structure['类型']}:")
print(f" HTML: {structure['HTML'][:80]}...")
print(f" 规则:")
for key, value in structure['rules'].items():
print(f" {key}: {value}")
print()
# ============================================
# 主函数
# ============================================
def main():
"""运行所有示例"""
print("=" * 80)
print("Legado Book Source Developer Skill - 工作流示例")
print("=" * 80)
print()
# 运行示例
example_create_book_source()
example_css_selectors()
example_regex_patterns()
example_book_source_templates()
example_encoding_detection()
example_next_content_url()
example_html_structures()
print("=" * 80)
print("示例演示完成")
print("=" * 80)
if __name__ == "__main__":
main()
FILE:tools/analyze_url.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
通用网站分析工具 - 支持任意URL
Usage: python analyze_url.py <url> [--method POST] [--body "key={{key}}"] [--charset gbk]
"""
import sys, io, json, re, argparse, time
from urllib.parse import urlparse
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
try:
import requests
from bs4 import BeautifulSoup
except ImportError:
print("缺少依赖: pip install requests beautifulsoup4")
sys.exit(1)
DEFAULT_HEADERS = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
}
def detect_encoding(url, headers):
"""检测编码"""
try:
r = requests.get(url, headers=headers, timeout=15)
charset = r.apparent_encoding or r.encoding or 'utf-8'
return charset.lower(), r
except Exception as e:
return 'utf-8', None
def analyze_html(html, charset='utf-8'):
"""分析HTML结构"""
soup = BeautifulSoup(html, 'html.parser')
result = {
'title': soup.title.string if soup.title else None,
'charset': charset,
'forms': [],
'links': {'internal': 0, 'external': 0},
'images': len(soup.find_all('img')),
'scripts': len(soup.find_all('script')),
'possible_list_containers': [],
'possible_content_containers': [],
}
# 分析表单(搜索接口线索)
for form in soup.find_all('form'):
form_info = {
'action': form.get('action', ''),
'method': form.get('method', 'GET').upper(),
'inputs': []
}
for inp in form.find_all(['input', 'select']):
form_info['inputs'].append({
'name': inp.get('name', ''),
'type': inp.get('type', 'text'),
'value': inp.get('value', '')
})
result['forms'].append(form_info)
# 查找可能的列表容器
list_patterns = ['list', 'result', 'book', 'item', 'search', 'novel']
for tag in soup.find_all(['div', 'ul', 'ol', 'section']):
cls = ' '.join(tag.get('class', []))
id_ = tag.get('id', '')
combined = (cls + ' ' + id_).lower()
if any(p in combined for p in list_patterns):
children = tag.find_all(['li', 'div', 'a'], recursive=False)
if len(children) >= 3:
result['possible_list_containers'].append({
'tag': tag.name,
'class': cls,
'id': id_,
'child_count': len(children)
})
# 查找可能的内容容器
content_patterns = ['content', 'chapter', 'text', 'article', 'body']
for tag in soup.find_all(['div', 'article', 'section']):
cls = ' '.join(tag.get('class', []))
id_ = tag.get('id', '')
combined = (cls + ' ' + id_).lower()
if any(p in combined for p in content_patterns):
text_len = len(tag.get_text(strip=True))
if text_len > 200:
result['possible_content_containers'].append({
'tag': tag.name,
'class': cls,
'id': id_,
'text_length': text_len
})
return result
def find_search_urls(html, base_url):
"""识别搜索接口"""
soup = BeautifulSoup(html, 'html.parser')
candidates = []
for form in soup.find_all('form'):
action = form.get('action', '')
method = form.get('method', 'GET').upper()
if action:
from urllib.parse import urljoin
full_url = urljoin(base_url, action)
keyword_inputs = []
for inp in form.find_all('input'):
name = inp.get('name', '')
itype = inp.get('type', 'text')
if name and itype in ('text', 'search', 'hidden'):
keyword_inputs.append(name)
candidates.append({
'url': full_url,
'method': method,
'params': keyword_inputs,
'template': f"{full_url}?{keyword_inputs[0]}={{key}}" if keyword_inputs and method == 'GET' else None
})
return candidates
def main():
parser = argparse.ArgumentParser(description='通用网站分析')
parser.add_argument('url', help='目标URL')
parser.add_argument('--method', default='GET', help='HTTP方法')
parser.add_argument('--body', default=None, help='POST body模板')
parser.add_argument('--charset', default=None, help='强制编码')
parser.add_argument('--search-url', default=None, help='已知搜索URL')
args = parser.parse_args()
headers = DEFAULT_HEADERS.copy()
print("=" * 60)
print("步骤 1: 编码检测")
print("=" * 60)
detected_charset, resp = detect_encoding(args.url, headers)
charset = args.charset or detected_charset
print(f"检测到编码: {charset}")
if args.body:
print(f"\n步骤 2: POST请求 ({args.url})")
print(f"Body: {args.body}")
if 'gbk' in charset:
encoded_body = args.body.encode('gbk')
else:
encoded_body = args.body.encode('utf-8')
headers['Content-Type'] = 'application/x-www-form-urlencoded'
resp = requests.post(args.url, data=encoded_body, headers=headers, timeout=15)
elif not resp:
resp = requests.get(args.url, headers=headers, timeout=15)
print(f"\n状态码: {resp.status_code}")
print(f"内容长度: {len(resp.text)} 字符")
html = resp.text
print(f"\n{'=' * 60}")
print("步骤 3: HTML结构分析")
print("=" * 60)
analysis = analyze_html(html, charset)
print(json.dumps(analysis, ensure_ascii=False, indent=2))
print(f"\n{'=' * 60}")
print("步骤 4: 搜索接口识别")
print("=" * 60)
search_urls = find_search_urls(html, args.url)
if search_urls:
for su in search_urls:
print(f" 方法: {su['method']}")
print(f" URL: {su['url']}")
print(f" 参数: {su['params']}")
if su['template']:
print(f" 模板: {su['template']}")
print()
else:
print(" 未找到搜索表单,请手动检查 Network 面板")
# 保存HTML
safe_name = urlparse(args.url).netloc.replace('.', '_')
out_file = f"{safe_name}_analysis.html"
with open(out_file, 'w', encoding='utf-8') as f:
f.write(html)
print(f"\nHTML已保存: {out_file}")
if __name__ == '__main__':
main()
FILE:tools/analyze_url.sh
#!/bin/bash
# 通用网站快速分析 - 纯 bash + curl,无需 Python
# Usage: bash analyze_url.sh <url> [--post "body"] [--charset gbk]
set -euo pipefail
URL="?Usage: $0 <url> [--post body] [--charset charset]"
METHOD="GET"
BODY=""
CHARSET=""
OUTDIR="."
shift
while [[ $# -gt 0 ]]; do
case "$1" in
--post) METHOD="POST"; BODY="$2"; shift 2 ;;
--charset) CHARSET="$2"; shift 2 ;;
--outdir) OUTDIR="$2"; shift 2 ;;
*) shift ;;
esac
done
UA="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
echo "=========================================="
echo "网站分析: $URL"
echo "=========================================="
# Step 1: 检测编码
echo ""
echo "--- 编码检测 ---"
HEADERS=$(curl -sI --max-time 15 -A "$UA" "$URL" 2>/dev/null || true)
CT=$(echo "$HEADERS" | grep -i "content-type" | head -1 || true)
echo "Content-Type: -unknown"
if echo "$CT" | grep -qi "gbk\|gb2312"; then
[ -z "$CHARSET" ] && CHARSET="gbk"
fi
CHARSET="-utf-8"
echo "使用编码: $CHARSET"
# Step 2: 获取HTML
echo ""
echo "--- 获取页面 ---"
SAFENAME=$(echo "$URL" | sed 's|https\?://||;s|[^a-zA-Z0-9]|_|g')
OUTFILE="OUTDIR/SAFENAME.html"
if [ "$METHOD" = "POST" ]; then
curl -s --max-time 30 -A "$UA" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "$BODY" \
"$URL" > "$OUTFILE"
else
curl -s --max-time 30 -A "$UA" "$URL" > "$OUTFILE"
fi
SIZE=$(wc -c < "$OUTFILE")
LINES=$(wc -l < "$OUTFILE")
echo "HTML已保存: $OUTFILE ($SIZE bytes, $LINES lines)"
# Step 3: 基础分析
echo ""
echo "--- 基础分析 ---"
TITLE=$(grep -oP '<title[^>]*>\K[^<]+' "$OUTFILE" | head -1 || echo "N/A")
echo "标题: $TITLE"
FORMS=$(grep -c '<form' "$OUTFILE" || true)
echo "表单数: $FORMS"
IMGS=$(grep -c '<img' "$OUTFILE" || true)
echo "图片数: $IMGS"
SCRIPTS=$(grep -c '<script' "$OUTFILE" || true)
echo "脚本数: $SCRIPTS"
# Step 4: 搜索表单分析
echo ""
echo "--- 搜索表单 ---"
if [ "$FORMS" -gt 0 ]; then
grep -oP '<form[^>]*>.*?</form>' "$OUTFILE" 2>/dev/null | head -3 | while read -r form; do
ACTION=$(echo "$form" | grep -oP 'action="[^"]*"' | head -1 || true)
METHOD_F=$(echo "$form" | grep -oP 'method="[^"]*"' | head -1 || echo 'method="GET"')
INPUTS=$(echo "$form" | grep -oP '<input[^>]*>' | head -5 || true)
echo " $METHOD_F $ACTION"
echo " Inputs: $INPUTS"
done
else
echo " 未找到表单,请手动检查 Network 面板"
fi
# Step 5: 常见CSS选择器线索
echo ""
echo "--- CSS选择器线索 ---"
echo "可能的列表容器:"
grep -oP 'class="[^"]*\(list\|result\|book\|item\|search\)[^"]*"' "$OUTFILE" | sort -u | head -10 | sed 's/^/ /'
echo "可能的内容容器:"
grep -oP 'class="[^"]*\(content\|chapter\|text\|article\|body\)[^"]*"' "$OUTFILE" | sort -u | head -10 | sed 's/^/ /'
echo ""
echo "=========================================="
echo "分析完成。请用 web_fetch 或浏览器进一步确认选择器。"
echo "=========================================="
FILE:tools/js_param_analyzer.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
JavaScript参数分析工具
用于分析和提取JavaScript中的参数生成逻辑
"""
import sys
import io
import re
import json
from urllib.parse import urlparse, urljoin
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
try:
import requests
from bs4 import BeautifulSoup
except ImportError as e:
print(f"错误: 缺少依赖库 - {e}")
print("请运行: pip install requests beautifulsoup4")
sys.exit(1)
class JSParamAnalyzer:
"""JavaScript参数分析器"""
def __init__(self, base_url):
self.base_url = base_url.rstrip('/')
self.session = requests.Session()
self.session.headers.update({
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
})
self.js_functions = {}
self.api_endpoints = []
self.param_generators = {}
def extract_javascript_from_html(self, html):
"""从HTML中提取所有JavaScript代码"""
print("=" * 70)
print("步骤 1:从HTML中提取JavaScript")
print("=" * 70)
soup = BeautifulSoup(html, 'html.parser')
scripts = soup.find_all('script')
all_js = []
# 外部JS文件
external_js = []
for script in scripts:
src = script.get('src')
if src:
external_js.append(urljoin(self.base_url, src))
print(f"✓ 找到 {len(external_js)} 个外部JS文件")
# 内联脚本
inline_scripts = []
for script in scripts:
if script.string and script.string.strip():
inline_scripts.append(script.string)
print(f"✓ 找到 {len(inline_scripts)} 个内联脚本")
return external_js, inline_scripts
def download_and_analyze_js(self, js_urls):
"""下载并分析JS文件"""
print("\n" + "=" * 70)
print("步骤 2:下载并分析JavaScript文件")
print("=" * 70)
for i, js_url in enumerate(js_urls, 1):
print(f"\n[{i}/{len(js_urls)}] 分析: {js_url}")
try:
response = self.session.get(js_url, timeout=15)
js_content = response.text
# 保存JS文件
filename = js_url.split('/')[-1]
if not filename.endswith('.js'):
filename += '.js'
with open(filename, 'w', encoding='utf-8') as f:
f.write(js_content)
print(f" ✓ 已保存: {filename} ({len(js_content)} 字符)")
# 分析JS内容
self.analyze_js_functions(js_content, filename)
self.find_api_endpoints(js_content, filename)
self.find_param_generators(js_content, filename)
except Exception as e:
print(f" ✗ 下载失败: {str(e)[:100]}")
def analyze_js_functions(self, js_content, filename):
"""分析JavaScript中的函数"""
# 查找函数定义
function_patterns = [
r'function\s+(\w+)\s*\(',
r'(\w+)\s*:\s*function\s*\(',
r'const\s+(\w+)\s*=\s*\(',
r'let\s+(\w+)\s*=\s*\(',
r'var\s+(\w+)\s*=\s*function\s*\('
]
functions = []
for pattern in function_patterns:
matches = re.findall(pattern, js_content)
functions.extend(matches)
if functions:
print(f" • 找到 {len(set(functions))} 个函数")
# 查找与搜索相关的函数
search_related = [f for f in set(functions)
if any(keyword in f.lower()
for keyword in ['search', 'ajax', 'fetch', 'api', 'query', 'get'])]
if search_related:
print(f" 搜索相关函数: {', '.join(search_related[:10])}")
self.js_functions[filename] = search_related
def find_api_endpoints(self, js_content, filename):
"""查找API端点"""
api_patterns = [
r'["\']([^"\']*\/(?:api|ajax|search|query)[^"\']*)["\']',
r'url\s*[:=]\s*["\']([^"\']+)["\']',
r'baseURL\s*[:=]\s*["\']([^"\']+)["\']',
r'apiUrl\s*[:=]\s*["\']([^"\']+)["\']',
r'endpoint\s*[:=]\s*["\']([^"\']+)["\']',
r'fetch\s*\(\s*["\']([^"\']+)["\']',
r'\.get\s*\(\s*["\']([^"\']+)["\']',
r'\.post\s*\(\s*["\']([^"\']+)["\']'
]
endpoints = []
for pattern in api_patterns:
matches = re.findall(pattern, js_content, re.IGNORECASE)
endpoints.extend(matches)
if endpoints:
# 去重
unique_endpoints = list(set(endpoints))
print(f" • 找到 {len(unique_endpoints)} 个可能的API端点")
# 优先显示包含search/api/关键词的
priority_endpoints = [e for e in unique_endpoints
if any(kw in e.lower() for kw in ['search', 'api', 'ajax', 'query'])]
if priority_endpoints:
print(f" 优先端点: {', '.join(priority_endpoints[:5])}")
self.api_endpoints.extend(priority_endpoints)
else:
print(f" 其他端点: {', '.join(unique_endpoints[:5])}")
def find_param_generators(self, js_content, filename):
"""查找参数生成逻辑"""
# 查找加密/签名相关代码
encrypt_patterns = [
r'function\s+(\w*[Ee]ncrypt\w*)\s*\(',
r'function\s+(\w*[Ss]ign\w*)\s*\(',
r'function\s+(\w*[Hh]ash\w*)\s*\(',
r'function\s+(\w*[Tt]oken\w*)\s*\(',
r'(?:const|let|var)\s+(\w*[Ee]ncrypt\w*)\s*=',
r'(?:const|let|var)\s+(\w*[Ss]ign\w*)\s*=',
r'(?:const|let|var)\s+(\w*[Tt]oken\w*)\s*='
]
param_funcs = []
for pattern in encrypt_patterns:
matches = re.findall(pattern, js_content)
param_funcs.extend(matches)
if param_funcs:
print(f" • 找到 {len(set(param_funcs))} 个参数生成相关函数:")
for func in set(param_funcs)[:5]:
print(f" - {func}")
self.param_generators[filename] = self.param_generators.get(filename, []) + [func]
# 查找常用的加密库
crypto_libraries = [
'crypto-js',
'md5',
'sha',
'aes',
'des',
'rsa'
]
for lib in crypto_libraries:
if lib.lower() in js_content.lower():
print(f" • 可能使用的加密库: {lib}")
def extract_search_logic(self, js_content, filename):
"""提取搜索相关逻辑"""
print(f"\n 搜索逻辑分析:")
# 查找搜索函数调用
search_call_patterns = [
r'(?:search|query)\s*\([^)]*\)',
r'fetch\s*\([^)]*search[^)]*\)',
r'ajax\s*\([^)]*search[^)]*\)'
]
for pattern in search_call_patterns:
matches = re.findall(pattern, js_content, re.IGNORECASE)
if matches:
print(f" • 找到搜索调用:")
for match in matches[:3]:
print(f" {match[:100]}")
def generate_analysis_report(self):
"""生成分析报告"""
print("\n" + "=" * 70)
print("参数分析报告")
print("=" * 70)
print(f"\n网站: {self.base_url}")
if self.js_functions:
print(f"\n找到的搜索相关函数:")
for filename, funcs in self.js_functions.items():
print(f" {filename}: {', '.join(funcs)}")
if self.api_endpoints:
print(f"\n找到的API端点:")
for i, endpoint in enumerate(set(self.api_endpoints), 1):
print(f" {i}. {endpoint}")
if self.param_generators:
print(f"\n找到的参数生成函数:")
for filename, funcs in self.param_generators.items():
print(f" {filename}: {', '.join(funcs)}")
# 保存报告
report = {
'website': self.base_url,
'search_functions': self.js_functions,
'api_endpoints': list(set(self.api_endpoints)),
'param_generators': self.param_generators
}
with open('js_param_report.json', 'w', encoding='utf-8') as f:
json.dump(report, f, ensure_ascii=False, indent=2)
print(f"\n✓ 详细报告已保存: js_param_report.json")
# 提供建议
print("\n" + "=" * 70)
print("下一步建议")
print("=" * 70)
if not self.api_endpoints:
print("\n⚠️ 未找到明显的API端点")
print(" 建议:")
print(" 1. 使用浏览器开发者工具的Network面板")
print(" 2. 执行搜索操作,查看实际发出的请求")
print(" 3. 复制cURL并提供给分析工具")
else:
print("\n✓ 找到可能的API端点")
print(" 建议:")
print(" 1. 使用requests库测试这些端点")
print(" 2. 分析必需的参数")
print(" 3. 查看参数生成函数的实现")
if self.param_generators:
print("\n✓ 找到参数生成函数")
print(" 建议:")
print(" 1. 查看这些函数的具体实现")
print(" 2. 确定是否可以在Python中实现")
print(" 3. 或使用webView执行JavaScript")
def analyze_curl_command(self, curl_command):
"""分析cURL命令,提取请求信息"""
print("\n" + "=" * 70)
print("分析cURL命令")
print("=" * 70)
# 提取URL
url_match = re.search(r"curl\s+['\"]?([^'\"]+)['\"]?", curl_command)
if url_match:
print(f"URL: {url_match.group(1)}")
# 提取方法
method = 'GET'
if '-X POST' in curl_command or '--request POST' in curl_command:
method = 'POST'
print(f"Method: {method}")
# 提取请求头
headers = {}
header_pattern = r'-H\s+[\'\"]([^\'=]+):\s*([^\'\"]+)[\'\"]'
for match in re.finditer(header_pattern, curl_command):
key = match.group(1)
value = match.group(2)
headers[key] = value
if headers:
print(f"\nHeaders:")
for key, value in headers.items():
print(f" {key}: {value}")
# 提取数据
data_pattern = r'--data-?[a-z]*\s+[\'\"]([^\'\"]+)[\'\"]|-d\s+[\'\"]([^\'\"]+)[\'\"]'
data_match = re.search(data_pattern, curl_command)
if data_match:
data = data_match.group(1) or data_match.group(2)
print(f"\nData: {data}")
# 尝试解析JSON数据
if data.startswith('{'):
try:
json_data = json.loads(data)
print(f"\nJSON参数:")
for key, value in json_data.items():
print(f" {key}: {value}")
except:
pass
def main():
"""主函数"""
if len(sys.argv) < 2:
print("用法: python js_param_analyzer.py <选项>")
print("\n选项:")
print(" --analyze <URL> 分析网站的JavaScript")
print(" --curl <命令> 分析cURL命令")
print("\n示例:")
print(" python js_param_analyzer.py --analyze https://www.example.com")
print(" python js_param_analyzer.py --curl 'curl http://example.com...'")
sys.exit(1)
option = sys.argv[1]
try:
if option == '--analyze':
if len(sys.argv) < 3:
print("错误: 需要提供网站URL")
sys.exit(1)
url = sys.argv[2]
print(f"分析网站: {url}\n")
analyzer = JSParamAnalyzer(url)
# 获取首页HTML
response = analyzer.session.get(url, timeout=15)
html = response.text
# 保存首页
with open('home.html', 'w', encoding='utf-8') as f:
f.write(html)
# 提取JS
external_js, inline_scripts = analyzer.extract_javascript_from_html(html)
# 下载并分析
analyzer.download_and_analyze_js(external_js[:5]) # 只分析前5个
# 生成报告
analyzer.generate_analysis_report()
elif option == '--curl':
if len(sys.argv) < 3:
print("错误: 需要提供cURL命令")
sys.exit(1)
curl_command = ' '.join(sys.argv[2:])
analyzer = JSParamAnalyzer('unknown')
analyzer.analyze_curl_command(curl_command)
else:
print(f"错误: 未知选项 {option}")
sys.exit(1)
except Exception as e:
print(f"\n✗ 分析失败: {e}")
import traceback
traceback.print_exc()
sys.exit(1)
if __name__ == '__main__':
main()
FILE:tools/quick_analyze.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
快速网站分析工具 - 增强版 v2.1
用于快速分析网站结构、编码和搜索接口
优化:自动存储HTML到html_storage,输出符合JSON格式的书源
"""
import sys
import io
import json
import re
import time
import hashlib
from urllib.parse import urlparse, urljoin
from datetime import datetime
from pathlib import Path
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
try:
import requests
from bs4 import BeautifulSoup
except ImportError as e:
print(f"错误: 缺少依赖库 - {e}")
print("请运行: pip install requests beautifulsoup4")
sys.exit(1)
# 默认请求头
DEFAULT_HEADERS = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
'Accept-Encoding': 'gzip, deflate, br',
'Connection': 'keep-alive',
'Upgrade-Insecure-Requests': '1'
}
class HTMLStorage:
"""HTML存储管理器 - 存储到references/html_storage"""
def __init__(self, storage_dir='references/html_storage'):
self.storage_dir = Path(storage_dir)
self.storage_dir.mkdir(parents=True, exist_ok=True)
def generate_hash(self, url):
"""根据URL生成唯一哈希值"""
return hashlib.md5(url.encode('utf-8')).hexdigest()
def save_html(self, url, content, page_type='page'):
"""
保存HTML到storage目录
Args:
url: 原始URL
content: HTML内容
page_type: 页面类型 (home, search, detail, toc, content)
Returns:
dict: 包含文件路径和元数据的字典
"""
file_hash = self.generate_hash(url)
html_filename = self.storage_dir / f"{file_hash}.html"
meta_filename = self.storage_dir / f"{file_hash}.meta.json"
# 保存HTML文件
with open(html_filename, 'w', encoding='utf-8') as f:
f.write(content)
# 生成元数据
metadata = {
'url': url,
'size': len(content),
'timestamp': time.time(),
'datetime': datetime.now().isoformat(),
'page_type': page_type,
'storage_path': str(html_filename.relative_to(self.storage_dir.parent))
}
# 保存元数据JSON
with open(meta_filename, 'w', encoding='utf-8') as f:
json.dump(metadata, f, ensure_ascii=False, indent=2)
return {
'hash': file_hash,
'html_file': str(html_filename),
'meta_file': str(meta_filename),
'metadata': metadata
}
class QuickAnalyzer:
"""快速网站分析器"""
def __init__(self, base_url, storage_dir='references/html_storage', headers=None):
self.base_url = base_url.rstrip('/')
self.headers = headers or DEFAULT_HEADERS
self.session = requests.Session()
self.session.headers.update(self.headers)
self.storage = HTMLStorage(storage_dir)
self.info = {
'base_url': base_url,
'encoding': None,
'charset': None,
'html_files': {},
'js_files': {},
'search_patterns': [],
'detected_selectors': {},
'book_source_draft': None
}
def detect_encoding(self):
"""检测网站编码并保存首页"""
print("=" * 70)
print("步骤 1:检测网站编码并保存首页")
print("=" * 70)
try:
response = self.session.get(self.base_url, timeout=15)
print(f"✓ 状态码: {response.status_code}")
print(f"✓ 响应编码: {response.encoding}")
print(f"✓ Content-Type: {response.headers.get('Content-Type', 'N/A')}")
print(f"✓ 实际编码: {response.apparent_encoding}")
print(f"✓ 页面长度: {len(response.text)} 字符")
# 确定最终编码
content_type = response.headers.get('Content-Type', '')
charset_match = re.search(r'charset=([^\s;]+)', content_type, re.I)
if charset_match:
detected_charset = charset_match.group(1).lower()
else:
detected_charset = response.apparent_encoding
self.info['encoding'] = detected_charset
self.info['charset'] = detected_charset
# 保存首页到html_storage
saved = self.storage.save_html(self.base_url, response.text, page_type='home')
self.info['html_files']['home'] = saved
print(f"✓ 首页已保存: {saved['html_file']}")
print(f"✓ 元数据: {saved['meta_file']}")
return response.text
except Exception as e:
print(f"✗ 错误: {e}")
raise
def find_javascript_files(self, html):
"""查找并保存JavaScript文件"""
print("\n" + "=" * 70)
print("步骤 2:查找JavaScript文件")
print("=" * 70)
soup = BeautifulSoup(html, 'html.parser')
scripts = soup.find_all('script')
js_urls = []
for script in scripts:
if script.get('src'):
src = script.get('src')
# 转换为绝对URL
absolute_url = urljoin(self.base_url, src)
js_urls.append(absolute_url)
print(f"✓ 找到 {len(js_urls)} 个外部JavaScript文件")
# 下载并保存JS文件
for i, js_url in enumerate(js_urls[:10], 1): # 只下载前10个
try:
print(f" [{i}/{min(len(js_urls), 10)}] 下载: {js_url}")
response = self.session.get(js_url, timeout=10)
js_content = response.text
# 保存JS文件
js_hash = hashlib.md5(js_url.encode('utf-8')).hexdigest()
js_filename = self.storage.storage_dir / f"{js_hash}.js"
with open(js_filename, 'w', encoding='utf-8') as f:
f.write(js_content)
# 保存元数据
meta_filename = self.storage.storage_dir / f"{js_hash}.js.meta.json"
metadata = {
'url': js_url,
'type': 'javascript',
'size': len(js_content),
'timestamp': time.time(),
'datetime': datetime.now().isoformat(),
'storage_path': str(js_filename.relative_to(self.storage.storage_dir.parent))
}
with open(meta_filename, 'w', encoding='utf-8') as f:
json.dump(metadata, f, ensure_ascii=False, indent=2)
self.info['js_files'][js_url] = {
'file': str(js_filename),
'meta': str(meta_filename)
}
print(f" ✓ 已保存: {js_filename.name}")
except Exception as e:
print(f" ✗ 下载失败: {e}")
return js_urls
def analyze_search_patterns(self, html):
"""分析搜索接口模式"""
print("\n" + "=" * 70)
print("步骤 3:分析搜索接口模式")
print("=" * 70)
soup = BeautifulSoup(html, 'html.parser')
patterns = []
# 查找搜索表单
search_forms = soup.find_all('form')
print(f"✓ 找到 {len(search_forms)} 个表单")
for form in search_forms:
form_action = form.get('action', '')
if 'search' in form_action.lower() or 's.' in form_action:
absolute_url = urljoin(self.base_url, form_action)
method = form.get('method', 'GET').upper()
print(f"\n 表单: {absolute_url}")
print(f" 方法: {method}")
inputs = form.find_all('input')
params = []
for inp in inputs:
name = inp.get('name', '')
inp_type = inp.get('type', 'text')
if name:
params.append({
'name': name,
'type': inp_type
})
print(f" 参数: {name} ({inp_type})")
patterns.append({
'type': 'form',
'url': absolute_url,
'method': method,
'params': params
})
# 查找搜索链接
search_links = soup.find_all('a', href=re.compile(r'search|s\.'))
if search_links:
print(f"\n✓ 找到 {len(search_links)} 个搜索相关链接")
for link in search_links[:5]: # 只显示前5个
href = link.get('href', '')
text = link.get_text(strip=True)
absolute_url = urljoin(self.base_url, href)
patterns.append({
'type': 'link',
'url': absolute_url,
'text': text
})
print(f" 链接: {text} -> {absolute_url}")
self.info['search_patterns'] = patterns
return patterns
def analyze_html_structure(self, html):
"""分析HTML结构并推断选择器"""
print("\n" + "=" * 70)
print("步骤 4:分析HTML结构")
print("=" * 70)
soup = BeautifulSoup(html, 'html.parser')
detected = {}
# 常见的书籍列表选择器
book_list_selectors = [
'.book-list', '.result-list', '.search-result',
'ul.hot_sale', '#sitebox dl', '#content .list li'
]
for selector in book_list_selectors:
elements = soup.select(selector)
if elements:
print(f"✓ 找到书籍列表: {selector} ({len(elements)} 个)")
detected['bookList'] = selector
# 分析第一个书籍项的结构
first_item = elements[0]
print(f"\n 第一个书籍项的结构:")
print(f" {first_item.prettify()[:500]}...")
# 尝试推断各个字段的选择器
title_selectors = ['.title', 'h3', 'h4', 'a', '.name']
for title_sel in title_selectors:
title = first_item.select_one(title_sel)
if title:
print(f" ✓ 书名选择器: {selector} {title_sel}@text")
detected['name'] = title_sel
break
author_selectors = ['.author', '.writer', '.by', 'p:nth-child(2)']
for author_sel in author_selectors:
author = first_item.select_one(author_sel)
if author:
print(f" ✓ 作者选择器: {selector} {author_sel}@text")
detected['author'] = author_sel
break
cover_selectors = ['img', '.cover img', '.book-cover']
for cover_sel in cover_selectors:
cover = first_item.select_one(cover_sel)
if cover:
# 检查懒加载
if cover.get('data-original'):
print(f" ✓ 封面选择器: {selector} {cover_sel}@data-original||{cover_sel}@src")
detected['coverUrl'] = f"{cover_sel}@data-original||{cover_sel}@src"
else:
print(f" ✓ 封面选择器: {selector} {cover_sel}@src")
detected['coverUrl'] = f"{cover_sel}@src"
break
link_selectors = ['a', 'a[href]']
for link_sel in link_selectors:
link = first_item.select_one(link_sel)
if link:
print(f" ✓ 链接选择器: {selector} {link_sel}@href")
detected['bookUrl'] = link_sel
break
break
self.info['detected_selectors'] = detected
return detected
def generate_book_source_json(self):
"""生成符合标准格式的书源JSON"""
print("\n" + "=" * 70)
print("步骤 5:生成书源JSON")
print("=" * 70)
# 基础信息
parsed_url = urlparse(self.base_url)
book_source_url = f"{parsed_url.scheme}://{parsed_url.netloc}"
book_source_name = f"示例书源_{parsed_url.netloc.replace('.', '_')}"
# 构建searchUrl
search_patterns = self.info.get('search_patterns', [])
if search_patterns:
pattern = search_patterns[0]
if pattern['type'] == 'form':
search_url = f"{pattern['url']}"
# 添加参数配置
config = {}
if pattern['method'] != 'GET':
config['method'] = pattern['method']
if self.info.get('charset') == 'gbk':
config['charset'] = 'gbk'
if config:
search_url = f"{search_url},{json.dumps(config)}"
else:
search_url = pattern['url']
else:
# 默认搜索URL
search_url = "/search?q={{key}}"
if self.info.get('charset') == 'gbk':
search_url = f"{search_url},{{\"charset\":\"gbk\"}}"
# 获取检测到的选择器
selectors = self.info.get('detected_selectors', {})
# 构建书源JSON - 严格符合Legado格式
book_source = {
"bookSourceUrl": book_source_url,
"bookSourceName": book_source_name,
"bookSourceType": 0,
"bookSourceGroup": "默认分组",
"enabled": True,
"enabledExplore": True,
"enabledCookieJar": True,
"loginUrl": "",
"loginUi": "",
"loginCheckJs": "",
"concurrentRate": "",
"header": json.dumps(DEFAULT_HEADERS, ensure_ascii=False),
"searchUrl": search_url,
"exploreUrl": "",
"ruleSearch": {
"bookList": selectors.get('bookList', '.book-list'),
"name": selectors.get('name', '.title@text'),
"author": selectors.get('author', '.author@text'),
"kind": "",
"wordCount": "",
"lastChapter": "",
"intro": "",
"coverUrl": selectors.get('coverUrl', 'img@src'),
"bookUrl": selectors.get('bookUrl', 'a@href')
},
"ruleBookInfo": {
"name": "",
"author": "",
"kind": "",
"wordCount": "",
"lastChapter": "",
"intro": "",
"coverUrl": "",
"init": ""
},
"ruleToc": {
"chapterList": "",
"chapterName": "",
"chapterUrl": "",
"formatJs": "",
"nextTocUrl": ""
},
"ruleContent": {
"content": "",
"replaceRegex": "",
"imageStyle": "",
"imageDecode": "",
"webJs": "",
"nextContentUrl": "",
"title": ""
},
"ruleExplore": {},
"ruleReview": {},
"bookSourceComment": f"由quick_analyze.py自动生成\\nURL: {self.base_url}\\n编码: {self.info.get('encoding', 'UTF-8')}",
"variableComment": "{}"
}
self.info['book_source_draft'] = book_source
# 保存书源JSON到文件
output_file = f"book_source_{int(time.time())}.json"
with open(output_file, 'w', encoding='utf-8') as f:
json.dump([book_source], f, ensure_ascii=False, indent=2)
print(f"\n✓ 书源JSON已生成: {output_file}")
print(f"\n书源信息:")
print(f" 书源名称: {book_source['bookSourceName']}")
print(f" 书源URL: {book_source['bookSourceUrl']}")
print(f" 搜索URL: {book_source['searchUrl']}")
print(f" 编码: {self.info.get('encoding', 'UTF-8')}")
return book_source, output_file
def save_analysis_report(self):
"""保存分析报告"""
print("\n" + "=" * 70)
print("步骤 6:保存分析报告")
print("=" * 70)
report = {
'timestamp': time.time(),
'datetime': datetime.now().isoformat(),
'base_url': self.base_url,
'encoding': self.info.get('encoding'),
'charset': self.info.get('charset'),
'html_files': {k: v['html_file'] if isinstance(v, dict) else v for k, v in self.info.get('html_files', {}).items()},
'js_files_count': len(self.info.get('js_files', {})),
'search_patterns': self.info.get('search_patterns', []),
'detected_selectors': self.info.get('detected_selectors', {}),
'book_source_draft': self.info.get('book_source_draft')
}
report_file = f"analysis_report_{int(time.time())}.json"
with open(report_file, 'w', encoding='utf-8') as f:
json.dump(report, f, ensure_ascii=False, indent=2)
print(f"✓ 分析报告已保存: {report_file}")
print(f"\n报告内容:")
print(f" 编码: {report['encoding']}")
print(f" HTML文件: {len(report['html_files'])} 个")
print(f" JS文件: {report['js_files_count']} 个")
print(f" 搜索模式: {len(report['search_patterns'])} 个")
print(f" 检测到的选择器: {len(report['detected_selectors'])} 个")
return report_file
def run(self):
"""执行完整分析流程"""
print("\n" + "=" * 70)
print("Legado 书源快速分析工具 v2.1")
print("=" * 70)
print(f"目标网站: {self.base_url}")
print(f"存储目录: {self.storage.storage_dir.absolute()}")
print("=" * 70 + "\n")
try:
# 步骤1: 检测编码并保存首页
html = self.detect_encoding()
# 步骤2: 查找JS文件
self.find_javascript_files(html)
# 步骤3: 分析搜索模式
self.analyze_search_patterns(html)
# 步骤4: 分析HTML结构
self.analyze_html_structure(html)
# 步骤5: 生成书源JSON
book_source, output_file = self.generate_book_source_json()
# 步骤6: 保存分析报告
report_file = self.save_analysis_report()
print("\n" + "=" * 70)
print("✓ 分析完成!")
print("=" * 70)
print(f"\n生成的文件:")
print(f" 1. 书源JSON: {output_file}")
print(f" 2. 分析报告: {report_file}")
print(f" 3. HTML文件: references/html_storage/*.html")
print(f" 4. JS文件: references/html_storage/*.js")
print(f"\n下一步:")
print(f" 1. 查看生成的书源JSON: cat {output_file}")
print(f" 2. 根据实际情况修改规则")
print(f" 3. 导入Legado测试")
return book_source, output_file
except Exception as e:
print(f"\n✗ 分析失败: {e}")
import traceback
traceback.print_exc()
return None, None
def main():
"""主函数"""
if len(sys.argv) < 2:
print("使用方法: python quick_analyze.py <URL>")
print("示例: python quick_analyze.py https://www.example.com")
sys.exit(1)
url = sys.argv[1]
analyzer = QuickAnalyzer(url)
analyzer.run()
if __name__ == "__main__":
main()
FILE:tools/quick_search_url_extractor.py
#!/usr/bin/env python3
"""
快速获取搜索地址工具 - 基于快速写源订阅源的智能分析
功能:
1. 自动检测网站编码
2. 下载并分析网页
3. 智能识别搜索表单
4. 自动生成搜索地址
5. 可选:生成书源草稿
"""
import sys
import json
import time
import re
import hashlib
from pathlib import Path
from datetime import datetime
from urllib.parse import urljoin, urlparse
import requests
from bs4 import BeautifulSoup
class QuickSearchUrlExtractor:
"""快速搜索地址提取器"""
def __init__(self, url, headers=None, timeout=10):
self.url = url
self.headers = headers or {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
}
self.timeout = timeout
self.base_url = self._get_base_url()
def _get_base_url(self):
"""提取基础URL"""
parsed = urlparse(self.url)
return f"{parsed.scheme}://{parsed.netloc}"
def fetch_page(self, url=None):
"""获取网页内容"""
target_url = url or self.url
print(f"正在获取: {target_url}")
try:
response = requests.get(
target_url,
headers=self.headers,
timeout=self.timeout,
allow_redirects=True
)
# 检测编码
charset = self._detect_charset(response)
if charset:
response.encoding = charset
print(f"✓ 状态码: {response.status_code}")
print(f"✓ 编码: {charset or 'UTF-8'}")
print(f"✓ 大小: {len(response.text)} 字符")
return response.text, charset
except Exception as e:
print(f"✗ 获取失败: {e}")
return None, None
def _detect_charset(self, response):
"""检测页面编码"""
# 优先从响应头获取
content_type = response.headers.get('Content-Type', '')
charset_match = re.search(r'charset=([^\s;]+)', content_type, re.I)
if charset_match:
return charset_match.group(1).upper()
# 从HTML meta标签获取
html = response.text
charset_patterns = [
r'<meta[^>]+charset=["\']?([^\s"\'>]+)',
r'<meta[^>]+content=["\'][^"\']*charset=([^\s"\'>]+)'
]
for pattern in charset_patterns:
match = re.search(pattern, html, re.I)
if match:
return match.group(1).upper()
return 'UTF-8' # 默认
def analyze_forms(self, html):
"""分析页面中的表单"""
soup = BeautifulSoup(html, 'html.parser')
forms = soup.find_all('form')
print(f"\n找到 {len(forms)} 个表单")
search_results = []
for idx, form in enumerate(forms, 1):
print(f"\n--- 表单 {idx} ---")
action = form.get('action', '')
method = form.get('method', 'GET').upper()
# 转换为绝对URL
if action:
action = urljoin(self.base_url, action)
print(f"Action: {action}")
print(f"Method: {method}")
# 分析表单字段
inputs = form.find_all(['input', 'select', 'textarea'])
print(f"字段数: {len(inputs)}")
fields = []
for inp in inputs:
name = inp.get('name', '')
field_type = inp.get('type', 'text')
value = inp.get('value', '')
placeholder = inp.get('placeholder', '')
fields.append({
'name': name,
'type': field_type,
'value': value,
'placeholder': placeholder
})
print(f" - {name} (type={field_type}, value={value}, placeholder={placeholder})")
# 智能识别搜索字段
search_field = self._identify_search_field(fields)
if search_field:
# 生成搜索地址
search_url = self._generate_search_url(action, method, fields, search_field)
search_results.append({
'index': idx,
'action': action,
'method': method,
'search_field': search_field,
'search_url': search_url
})
print(f"\n✓ 识别为搜索表单!")
print(f"✓ 搜索字段: {search_field}")
print(f"✓ 搜索地址: {search_url}")
else:
print(f"\n✗ 未识别为搜索表单")
return search_results
def _identify_search_field(self, fields):
"""智能识别搜索字段"""
# 策略1: 单个字段
if len(fields) == 1:
return fields[0]['name']
# 策略2: 常见搜索字段名
common_names = ['q', 'wd', 'query', 'search', 'key', 'keyword', 'k', 's']
for field in fields:
if field['name'].lower() in common_names:
return field['name']
# 策略3: 包含"search"/"key"的字段名
for field in fields:
if 'search' in field['name'].lower() or 'key' in field['name'].lower():
return field['name']
# 策略4: 文本类型且无默认值
for field in fields:
if field['type'] == 'text' and not field['value']:
return field['name']
return None
def _generate_search_url(self, action, method, fields, search_field):
"""生成搜索地址"""
# 构建参数
params = []
for field in fields:
if field['name'] == search_field:
value = '{{key}}'
elif field['value']:
value = field['value']
else:
# 尝试从select中获取默认值
value = ''
if value:
params.append(f"{field['name']}={value}")
body = '&'.join(params)
# 根据方法生成地址
if method == 'GET' or method == '':
if body:
return f"{action}?{body}"
return action
elif method == 'POST':
options = {'method': 'POST', 'body': body}
# 检测编码(如果不是UTF-8)
# 这里简化处理,实际应该检测
if not action.startswith('http'):
action = urljoin(self.base_url, action)
return f"{action},{json.dumps(options)}"
return action
def analyze_explore_links(self, html):
"""分析发现规则链接"""
soup = BeautifulSoup(html, 'html.parser')
# 常见的发现规则关键词
explore_keywords = [
'sort', 'list', 'rank', 'tag', 'shuku', 'fenlei',
'Soft', 'allvisit', 'paihang', 'quanben', 'gudian',
'lishi', 'dushi', 'wangyou', 'kehuan', 'yanqing',
'wuxia', 'xuanhuan', 'chuanyue', 'zhentan', 'kongbu',
'top', 'category', 'mulu'
]
# 构建选择器
explore_links = []
for keyword in explore_keywords:
links = soup.select(f'a[href*="{keyword}"]')
for link in links:
href = link.get('href', '')
text = link.get_text(strip=True)
# 排除数字和下一页等
if text and not text.isdigit() and text not in ['下一页', '登录', '注册', 'More+']:
explore_links.append({
'title': text,
'url': href
})
# 去重
seen = set()
unique_links = []
for link in explore_links:
if link['url'] not in seen:
seen.add(link['url'])
unique_links.append(link)
print(f"\n发现 {len(unique_links)} 个潜在的发现规则链接")
return unique_links[:20] # 限制数量
def generate_book_source_draft(self, search_results, explore_links, charset):
"""生成书源草稿"""
if not search_results:
return None
best_result = search_results[0]
search_url = best_result['search_url']
# 尝试获取OG元数据
html, _ = self.fetch_page()
soup = BeautifulSoup(html, 'html.parser')
# 提取OG标签
og_tags = {}
for meta in soup.find_all('meta', property=True):
og_tags[meta['property']] = meta.get('content', '')
# 构建书源草稿
source = {
'bookSourceName': f"快速生成的书源_{int(time.time())}",
'bookSourceUrl': self.base_url,
'bookSourceType': 0,
'bookSourceComment': f'//快速写源生成\n//创建时间: {datetime.now().isoformat()}',
'searchUrl': search_url,
'customOrder': 0,
'enabled': True,
'enabledCookieJar': False,
'ruleSearch': {
'bookList': '', # 需要用户补充
'name': '',
'author': '',
'kind': '',
'lastChapter': '',
'bookUrl': '',
'coverUrl': ''
},
'ruleBookInfo': {
'name': og_tags.get('og:novel:book_name', ''),
'author': og_tags.get('og:novel:author', ''),
'coverUrl': og_tags.get('og:image', ''),
'intro': og_tags.get('og:description', ''),
'kind': '',
'lastChapter': og_tags.get('og:novel:last_chapter_name', ''),
'tocUrl': ''
},
'ruleToc': {
'chapterList': '',
'chapterName': '',
'chapterUrl': '',
'nextTocUrl': ''
},
'ruleContent': {
'content': '',
'nextContentUrl': ''
},
'exploreUrl': '',
'weight': 0,
'respondTime': 180000,
'lastUpdateTime': int(time.time() * 1000)
}
# 如果有发现规则
if explore_links:
explore_rules = []
for link in explore_links[:10]:
# 转换为相对路径并支持分页
url = link['url']
# 替换末尾数字为{{page}}
url = re.sub(r'\d+(\.html?)(/)?$', r'{{page}}\1\2', url)
explore_rules.append(f"{link['title']}::{url}")
source['exploreUrl'] = ','.join(explore_rules)
source['enabledExplore'] = True
return source
def run(self):
"""运行分析"""
print("=" * 70)
print("快速搜索地址提取工具")
print("=" * 70)
print(f"目标网站: {self.url}")
print()
# 1. 获取页面
html, charset = self.fetch_page()
if not html:
return None
# 2. 分析表单
search_results = self.analyze_forms(html)
# 3. 分析发现规则
explore_links = self.analyze_explore_links(html)
# 4. 生成书源草稿
draft = self.generate_book_source_draft(search_results, explore_links, charset)
# 5. 输出结果
print("\n" + "=" * 70)
print("分析结果")
print("=" * 70)
if search_results:
print(f"\n✓ 成功识别 {len(search_results)} 个搜索表单\n")
for result in search_results:
print(f"表单 {result['index']}:")
print(f" 搜索字段: {result['search_field']}")
print(f" 搜索地址: {result['search_url']}")
print()
if explore_links:
print(f"✓ 发现规则 ({len(explore_links)} 个):")
for link in explore_links[:5]:
print(f" - {link['title']}: {link['url']}")
print()
if draft:
print("✓ 书源草稿已生成")
# 保存草稿
timestamp = int(time.time())
draft_file = Path(f'book_source_draft_{timestamp}.json')
with open(draft_file, 'w', encoding='utf-8') as f:
json.dump([draft], f, ensure_ascii=False, indent=2)
print(f" 草稿文件: {draft_file}")
print()
print("⚠️ 注意: 书源草稿需要您补充以下内容:")
print(" 1. ruleSearch 的所有规则")
print(" 2. ruleToc 的所有规则")
print(" 3. ruleContent 的所有规则")
print()
return {
'search_results': search_results,
'explore_links': explore_links,
'draft': draft
}
else:
print("\n✗ 未找到搜索表单")
print("\n建议:")
print(" 1. 检查URL是否正确")
print(" 2. 确认网站是否有搜索功能")
print(" 3. 尝试使用浏览器开发者工具手动分析")
return None
def main():
if len(sys.argv) < 2:
print("用法: python quick_search_url_extractor.py <URL>")
print("示例: python quick_search_url_extractor.py https://www.example.com")
sys.exit(1)
url = sys.argv[1]
extractor = QuickSearchUrlExtractor(url)
result = extractor.run()
if result:
print("=" * 70)
print("分析完成!")
print("=" * 70)
else:
print("=" * 70)
print("分析失败")
print("=" * 70)
sys.exit(1)
if __name__ == '__main__':
main()
FILE:tools/upload_book_source.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
直链上传工具 - Legado Book Source Developer
功能:
1. 验证书源内容是否合规
2. 修正不合规的书源格式
3. 将书源内容上传到图床/云存储
4. 返回可访问的直链
5. 支持多种上传方式配置
6. 美化返回结果
"""
import json
import requests
import os
import sys
from pathlib import Path
from typing import Dict, Any, Optional, Tuple, List
from datetime import datetime
# 设置Windows控制台输出编码
if sys.platform == 'win32':
import io
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8', errors='replace')
class BookSourceValidator:
"""书源JSON验证器"""
# Legado书源必需字段定义
REQUIRED_FIELDS = {
'bookSourceUrl': str,
'bookSourceName': str,
'bookSourceType': int,
'searchUrl': str,
'ruleSearch': dict,
'ruleToc': dict,
'ruleContent': dict
}
# 可选字段及其默认值
OPTIONAL_FIELDS_DEFAULTS = {
'bookSourceGroup': '',
'enabled': True,
'enabledExplore': True,
'enabledCookieJar': False,
'loginUrl': '',
'loginUi': '',
'loginCheckJs': '',
'concurrentRate': '',
'header': '',
'exploreUrl': '',
'ruleBookInfo': {},
'ruleExplore': {},
'ruleReview': {},
'bookSourceComment': '',
'variableComment': ''
}
def __init__(self):
self.errors = []
self.warnings = []
def validate_and_fix(self, book_source: Dict[str, Any]) -> Tuple[bool, Dict[str, Any], List[str], List[str]]:
"""
验证并修正书源JSON
Args:
book_source: 书源字典
Returns:
(is_valid, fixed_source, errors, warnings): 验证结果、修正后的书源、错误列表、警告列表
"""
self.errors = []
self.warnings = []
fixed_source = book_source.copy()
# 1. 检查是否是单个书源对象
if not isinstance(fixed_source, dict):
self.errors.append(f"书源必须是对象类型,当前类型: {type(fixed_source).__name__}")
return False, fixed_source, self.errors, self.warnings
# 2. 检查并添加必需字段
for field, expected_type in self.REQUIRED_FIELDS.items():
if field not in fixed_source:
if field == 'bookSourceType':
fixed_source[field] = 0
self.warnings.append(f"添加缺失的必需字段: {field} = 0")
elif field in ['ruleSearch', 'ruleToc', 'ruleContent']:
fixed_source[field] = {}
self.warnings.append(f"添加缺失的必需字段: {field} = {{}}")
elif field == 'searchUrl':
fixed_source[field] = ''
self.warnings.append(f"添加缺失的必需字段: {field} = ''")
else:
self.errors.append(f"缺少必需字段: {field}")
else:
# 检查类型是否正确
actual_value = fixed_source[field]
if not isinstance(actual_value, expected_type):
if expected_type == int and isinstance(actual_value, str):
# 尝试转换字符串到整数
try:
fixed_source[field] = int(actual_value)
self.warnings.append(f"修正字段类型: {field} 从字符串转换为整数")
except ValueError:
self.errors.append(
f"字段类型错误: {field} "
f"(期望: {expected_type.__name__}, 实际: {type(actual_value).__name__})"
)
elif expected_type == str and isinstance(actual_value, int):
# 整数到字符串可以接受
fixed_source[field] = str(actual_value)
self.warnings.append(f"修正字段类型: {field} 从整数转换为字符串")
elif actual_value is None:
# None值使用默认值
if field in self.OPTIONAL_FIELDS_DEFAULTS:
fixed_source[field] = self.OPTIONAL_FIELDS_DEFAULTS[field]
self.warnings.append(f"将None字段 {field} 设置为默认值")
else:
self.errors.append(f"字段 {field} 不能为None")
else:
self.errors.append(
f"字段类型错误: {field} "
f"(期望: {expected_type.__name__}, 实际: {type(actual_value).__name__})"
)
# 3. 检查并添加可选字段
for field, default_value in self.OPTIONAL_FIELDS_DEFAULTS.items():
if field not in fixed_source:
fixed_source[field] = default_value
self.warnings.append(f"添加缺失的可选字段: {field} = {repr(default_value)}")
# 4. 验证书源名称和URL
if not fixed_source.get('bookSourceName'):
self.errors.append("bookSourceName 不能为空")
elif not isinstance(fixed_source['bookSourceName'], str):
self.errors.append(f"bookSourceName 必须是字符串类型")
if not fixed_source.get('bookSourceUrl'):
self.errors.append("bookSourceUrl 不能为空")
elif not isinstance(fixed_source['bookSourceUrl'], str):
self.errors.append(f"bookSourceUrl 必须是字符串类型")
# 5. 验证规则对象
self._validate_rule_search(fixed_source.get('ruleSearch', {}))
self._validate_rule_toc(fixed_source.get('ruleToc', {}))
self._validate_rule_content(fixed_source.get('ruleContent', {}))
return len(self.errors) == 0, fixed_source, self.errors, self.warnings
def _validate_rule_search(self, rule_search: Dict[str, Any]):
"""验证搜索规则"""
if not isinstance(rule_search, dict):
self.errors.append("ruleSearch 必须是对象类型")
return
# bookList 和 bookUrl 是必需的
if not rule_search.get('bookList'):
self.errors.append("ruleSearch.bookList 不能为空")
if not rule_search.get('bookUrl'):
self.errors.append("ruleSearch.bookUrl 不能为空")
# name 是强烈推荐的
if not rule_search.get('name'):
self.warnings.append("建议添加 ruleSearch.name 字段")
def _validate_rule_toc(self, rule_toc: Dict[str, Any]):
"""验证目录规则"""
if not isinstance(rule_toc, dict):
self.errors.append("ruleToc 必须是对象类型")
return
# chapterList, chapterName, chapterUrl 是必需的
if not rule_toc.get('chapterList'):
self.errors.append("ruleToc.chapterList 不能为空")
if not rule_toc.get('chapterName'):
self.errors.append("ruleToc.chapterName 不能为空")
if not rule_toc.get('chapterUrl'):
self.errors.append("ruleToc.chapterUrl 不能为空")
def _validate_rule_content(self, rule_content: Dict[str, Any]):
"""验证正文规则"""
if not isinstance(rule_content, dict):
self.errors.append("ruleContent 必须是对象类型")
return
# content 是必需的
if not rule_content.get('content'):
self.errors.append("ruleContent.content 不能为空")
class BookSourceUploader:
"""书源直链上传器"""
def __init__(self):
self.references_dir = Path(__file__).parent.parent / 'references'
self.validator = BookSourceValidator()
def upload_book_source(
self,
book_source_json: Dict[str, Any],
upload_config: Optional[Dict[str, Any]] = None
) -> Tuple[bool, Dict[str, Any]]:
"""
上传书源内容到图床
Args:
book_source_json: 书源JSON对象
upload_config: 上传配置(如果为None则使用默认配置)
Returns:
(success, result): 成功标志和结果字典
"""
# 步骤1: 验证书源内容并尝试修正
is_valid, fixed_source, errors, warnings = self.validator.validate_and_fix(book_source_json)
# 显示验证结果
self._print_validation_result(is_valid, errors, warnings)
# 如果验证失败,不执行上传
if not is_valid:
return False, {
'error': '书源验证失败',
'details': '书源内容不符合Legado标准格式',
'errors': errors,
'warnings': warnings
}
# 使用修正后的书源进行上传
book_source_json = fixed_source
# 保存验证信息,用于后续显示
validation_info = {
'is_valid': is_valid,
'warnings_count': len(warnings),
'warnings': warnings
}
# 步骤2: 默认上传配置
if upload_config is None:
upload_config = {
"compress": False,
"downloadUrlRule": "$.data.links.url",
"summary": "鲸落图床",
# 默认图床:tu.406np.xyz 是公开的图片托管服务,用于生成书源分享直链
# 用户可通过 upload_config_example.json 自定义其他图床端点
"uploadUrl": "https://tu.406np.xyz/api/v1/upload",
"method": "POST",
"headers": {"Accept": "application/json"},
"body": {"file": "fileRequest", "show": "1"},
"type": "multipart/form-data"
}
try:
# 步骤3: 提取上传配置
upload_url = upload_config.get('uploadUrl', '')
method = upload_config.get('method', 'POST').upper()
headers = upload_config.get('headers', {})
body_config = upload_config.get('body', {})
content_type = upload_config.get('type', 'multipart/form-data')
download_rule = upload_config.get('downloadUrlRule', '$..url')
if not upload_url:
return False, {'error': '上传URL为空', 'details': 'uploadUrl配置缺失'}
# 准备上传内容 - 必须是数组格式
# Legado要求书源必须是JSON数组格式 [{...}],而不是单个对象 {...}
book_source_array = [book_source_json]
book_source_content = json.dumps(book_source_array, ensure_ascii=False, indent=2)
book_source_name = book_source_json.get('bookSourceName', 'unknown') + '.json'
# 验证JSON格式
is_valid_format, format_message = self._validate_json_array_format(book_source_content)
print(f"\n📋 JSON格式检查:")
print(f" {format_message}")
# 构建请求参数
files = {}
data = {}
for key, value in body_config.items():
if value == 'fileRequest':
# 文件字段 - 上传原始JSON内容
files[key] = (book_source_name, book_source_content, 'application/json')
else:
# 普通表单字段
data[key] = value
# 发送请求
if method == 'POST':
if content_type == 'multipart/form-data':
response = requests.post(upload_url, headers=headers, files=files, data=data, timeout=30)
else:
# JSON格式
headers['Content-Type'] = 'application/json'
json_body = {}
for key, value in body_config.items():
if value == 'fileRequest':
json_body[key] = book_source_content
else:
json_body[key] = value
response = requests.post(upload_url, headers=headers, json=json_body, timeout=30)
else:
return False, {'error': f'不支持的请求方法: {method}', 'details': '当前仅支持POST'}
# 检查响应
if response.status_code == 200:
result_data = response.json()
# 提取下载链接
download_url = self._extract_download_url(result_data, download_rule)
if download_url:
# 美化返回结果
formatted_result = self._format_success_result(
book_source_json,
upload_config,
result_data,
download_url,
validation_info
)
return True, formatted_result
else:
return False, {
'error': '无法提取下载链接',
'details': f'规则: {download_rule}',
'response': result_data
}
else:
return False, {
'error': f'上传失败: HTTP {response.status_code}',
'details': response.text
}
except requests.exceptions.Timeout:
return False, {'error': '请求超时', 'details': '上传服务响应超过30秒'}
except requests.exceptions.ConnectionError:
return False, {'error': '连接失败', 'details': '无法连接到上传服务器'}
except json.JSONDecodeError:
return False, {'error': '响应解析失败', 'details': '服务器返回的不是有效的JSON'}
except Exception as e:
return False, {'error': f'上传异常: {str(e)}', 'details': str(type(e).__name__)}
def _print_validation_result(self, is_valid: bool, errors: List[str], warnings: List[str]):
"""
打印验证结果
Args:
is_valid: 是否验证通过
errors: 错误列表
warnings: 警告列表
"""
print("\n" + "=" * 70)
print("📋 书源验证结果")
print("=" * 70)
if is_valid:
print("\n✅ 验证通过! 书源格式符合要求")
else:
print("\n❌ 验证失败! 书源格式存在问题")
if warnings:
print(f"\n⚠️ 发现 {len(warnings)} 个警告:")
for i, warning in enumerate(warnings, 1):
print(f" {i}. {warning}")
if errors:
print(f"\n🚫 发现 {len(errors)} 个错误:")
for i, error in enumerate(errors, 1):
print(f" {i}. {error}")
print("\n" + "=" * 70)
def _validate_json_array_format(self, json_string: str) -> Tuple[bool, str]:
"""
验证JSON字符串是否为数组格式
Args:
json_string: JSON字符串
Returns:
(is_valid, message): 是否有效和消息
"""
try:
data = json.loads(json_string)
if isinstance(data, list):
if len(data) == 0:
return False, "JSON数组为空"
elif len(data) == 1 and isinstance(data[0], dict):
return True, "✅ 正确的JSON数组格式 [{...}]"
else:
return True, f"✅ JSON数组格式 [{len(data)}个元素]"
elif isinstance(data, dict):
return False, "❌ 错误格式: JSON对象而非数组 (应为 [...], 实际为 {...})"
else:
return False, f"❌ 未知格式: {type(data).__name__}"
except json.JSONDecodeError as e:
return False, f"❌ JSON解析失败: {e}"
def _extract_download_url(self, data: Any, rule: str) -> Optional[str]:
"""
根据规则提取下载链接
Args:
data: 响应数据
rule: 提取规则(简化版JSONPath)
Returns:
提取的URL,如果失败返回None
"""
try:
if rule == '$':
return data
elif rule.startswith('$..'):
# JSONPath简化支持 - 深度查找
key = rule[3:]
if isinstance(data, dict):
return data.get(key)
elif isinstance(data, list) and len(data) > 0:
if isinstance(data[0], dict):
return data[0].get(key)
elif rule.startswith('$.'):
# JSONPath点路径支持 - 如 $.data.links.url
path = rule[2:].split('.')
current = data
for key in path:
if isinstance(current, dict):
current = current.get(key)
if current is None:
return None
else:
return None
return current
else:
# 直接访问
return data.get(rule if isinstance(rule, str) else rule)
except:
return None
return None
def _format_success_result(
self,
book_source: Dict[str, Any],
config: Dict[str, Any],
response: Dict[str, Any],
download_url: str,
validation_info: Optional[Dict[str, Any]] = None
) -> Dict[str, Any]:
"""
格式化成功结果(美化输出)
Args:
book_source: 书源对象
config: 上传配置
response: 服务器响应
download_url: 下载链接
validation_info: 验证信息(可选)
Returns:
格式化的结果字典
"""
result = {
'success': True,
'message': '✅ 书源上传成功!',
'book_source': {
'name': book_source.get('bookSourceName', '未命名'),
'url': book_source.get('bookSourceUrl', ''),
'group': book_source.get('bookSourceGroup', '')
},
'upload_info': {
'service': config.get('summary', '未知服务'),
'timestamp': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
'compress': config.get('compress', False)
},
'result': {
'download_url': download_url,
'raw_response': response
},
'usage': {
'import_method': '方法1: 复制链接后,在阅读APP中点击"导入书源",粘贴链接',
'import_method_2': '方法2: 直接打开链接,长按分享到阅读APP',
'qr_tip': '💡 提示:可以将链接生成二维码,扫描即可导入'
}
}
# 添加验证信息
if validation_info:
result['validation'] = validation_info
return result
def print_upload_result(result: Dict[str, Any]):
"""
打美化的上传结果
Args:
result: 上传结果字典
"""
if result.get('success'):
# 成功输出
print("\n" + "=" * 60)
print(f" {result['message']}")
print("=" * 60)
# 书源信息
print("\n📚 书源信息:")
print(f" 名称: {result['book_source']['name']}")
print(f" 地址: {result['book_source']['url']}")
print(f" 分组: {result['book_source']['group']}")
# 上传信息
print("\n📤 上传信息:")
print(f" 服务: {result['upload_info']['service']}")
print(f" 时间: {result['upload_info']['timestamp']}")
print(f" 压缩: {'是' if result['upload_info']['compress'] else '否'}")
# 验证信息
if 'validation' in result:
validation = result['validation']
print("\n📋 验证信息:")
print(f" 状态: {'✅ 通过' if validation.get('is_valid') else '❌ 失败'}")
if validation.get('warnings_count', 0) > 0:
print(f" 警告数: {validation['warnings_count']}")
print(" 警告内容:")
for warning in validation.get('warnings', [])[:5]: # 最多显示5个警告
print(f" - {warning}")
if validation['warnings_count'] > 5:
print(f" ... 还有 {validation['warnings_count'] - 5} 个警告")
# 下载链接
print("\n🔗 下载链接:")
print(f" {result['result']['download_url']}")
# 使用方法
print("\n💡 使用方法:")
print(f" {result['usage']['import_method']}")
print(f" {result['usage']['import_method_2']}")
print(f" {result['usage']['qr_tip']}")
print("\n" + "=" * 60)
else:
# 失败输出
print("\n" + "=" * 60)
print(" ❌ 上传失败")
print("=" * 60)
print(f"\n错误: {result.get('error', '未知错误')}")
if 'details' in result:
print(f"详情: {result['details']}")
# 显示验证错误
if 'errors' in result and result['errors']:
print("\n🚫 验证错误:")
for i, error in enumerate(result['errors'], 1):
print(f" {i}. {error}")
if 'warnings' in result and result['warnings']:
print("\n⚠️ 警告:")
for i, warning in enumerate(result['warnings'], 1):
print(f" {i}. {warning}")
print("\n" + "=" * 60)
def main():
"""命令行入口"""
import sys
if len(sys.argv) < 2:
print("用法:")
print(" python upload_book_source.py <书源JSON文件路径> [上传配置JSON文件路径]")
print("\n示例:")
print(" python upload_book_source.py references/book_source_database/my_source.json")
print(" python upload_book_source.py my_source.json custom_upload_config.json")
sys.exit(1)
# 读取书源文件
book_source_file = Path(sys.argv[1])
if not book_source_file.exists():
print(f"❌ 错误: 书源文件不存在: {book_source_file}")
sys.exit(1)
with open(book_source_file, 'r', encoding='utf-8') as f:
book_source_data = json.load(f)
# 如果是数组,取第一个元素
if isinstance(book_source_data, list) and len(book_source_data) > 0:
book_source = book_source_data[0]
print(f"✓ 检测到书源数组,使用第一个书源")
elif isinstance(book_source_data, dict):
book_source = book_source_data
else:
print(f"❌ 错误: 无效的书源格式")
sys.exit(1)
# 读取上传配置(可选)
upload_config = None
if len(sys.argv) >= 3:
config_file = Path(sys.argv[2])
if config_file.exists():
with open(config_file, 'r', encoding='utf-8') as f:
upload_config = json.load(f)
print(f"📝 使用自定义上传配置: {config_file}")
else:
print(f"⚠️ 警告: 配置文件不存在,使用默认配置: {config_file}")
# 执行上传
uploader = BookSourceUploader()
success, result = uploader.upload_book_source(book_source, upload_config)
# 打印结果
print_upload_result(result)
sys.exit(0 if success else 1)
if __name__ == '__main__':
main()
FILE:tools/validate_book_source.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
书源JSON验证工具
验证书源JSON是否符合Legado标准格式
"""
import sys
import io
import json
from pathlib import Path
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
class BookSourceValidator:
"""书源JSON验证器"""
# Legado书源必需字段定义
REQUIRED_FIELDS = {
'bookSourceUrl': str,
'bookSourceName': str,
'bookSourceType': int,
'bookSourceGroup': str,
'enabled': bool,
'enabledExplore': bool,
'enabledCookieJar': bool,
'loginUrl': str,
'loginUi': str,
'loginCheckJs': str,
'concurrentRate': str,
'header': str,
'searchUrl': str,
'exploreUrl': str,
'ruleSearch': dict,
'ruleBookInfo': dict,
'ruleToc': dict,
'ruleContent': dict,
'ruleExplore': dict,
'ruleReview': dict,
'bookSourceComment': str,
'variableComment': str
}
# ruleSearch必需字段
RULE_SEARCH_REQUIRED = {
'bookList': str,
'name': str,
'author': str,
'kind': str,
'wordCount': str,
'lastChapter': str,
'intro': str,
'coverUrl': str,
'bookUrl': str
}
# ruleToc必需字段
RULE_TOC_REQUIRED = {
'chapterList': str,
'chapterName': str,
'chapterUrl': str,
'formatJs': str,
'nextTocUrl': str
}
# ruleContent必需字段
RULE_CONTENT_REQUIRED = {
'content': str,
'replaceRegex': str,
'imageStyle': str,
'imageDecode': str,
'webJs': str,
'nextContentUrl': str,
'title': str
}
def __init__(self):
self.errors = []
self.warnings = []
def validate_json(self, json_file):
"""
验证书源JSON文件
Args:
json_file: JSON文件路径
Returns:
bool: 是否通过验证
"""
print("=" * 70)
print(f"验证书源JSON: {json_file}")
print("=" * 70)
# 1. 读取JSON文件
try:
with open(json_file, 'r', encoding='utf-8') as f:
data = json.load(f)
except FileNotFoundError:
self.errors.append(f"文件不存在: {json_file}")
return False
except json.JSONDecodeError as e:
self.errors.append(f"JSON格式错误: {e}")
return False
# 2. 检查是否是数组格式
if not isinstance(data, list):
self.errors.append(f"书源必须是数组格式,当前类型: {type(data).__name__}")
return False
if len(data) == 0:
self.errors.append("书源数组为空")
return False
print(f"✓ 找到 {len(data)} 个书源")
# 3. 验证书源对象
all_valid = True
for i, book_source in enumerate(data):
print(f"\n--- 验证书源 {i+1}/{len(data)} ---")
if not self.validate_book_source(book_source):
all_valid = False
# 4. 输出结果
print("\n" + "=" * 70)
print("验证结果")
print("=" * 70)
if self.errors:
print(f"\n✗ 发现 {len(self.errors)} 个错误:")
for error in self.errors:
print(f" - {error}")
if self.warnings:
print(f"\n⚠ 发现 {len(self.warnings)} 个警告:")
for warning in self.warnings:
print(f" - {warning}")
if all_valid and not self.errors:
print("\n✓ 验证通过!书源JSON格式正确")
return True
else:
print("\n✗ 验证失败!请修复上述错误")
return False
def validate_book_source(self, book_source):
"""验证单个书源对象"""
if not isinstance(book_source, dict):
self.errors.append(f"书源必须是对象类型,当前类型: {type(book_source).__name__}")
return False
# 检查必需字段
for field, expected_type in self.REQUIRED_FIELDS.items():
if field not in book_source:
self.errors.append(f"缺少必需字段: {field}")
continue
actual_type = type(book_source[field])
if not isinstance(book_source[field], expected_type):
# 允许None值
if book_source[field] is not None:
self.errors.append(
f"字段类型错误: {field} "
f"(期望: {expected_type.__name__}, 实际: {actual_type.__name__})"
)
continue
# 验证书源名称和URL
if book_source.get('bookSourceName'):
print(f" 书源名称: {book_source['bookSourceName']}")
else:
self.errors.append("bookSourceName 不能为空")
if book_source.get('bookSourceUrl'):
print(f" 书源URL: {book_source['bookSourceUrl']}")
else:
self.errors.append("bookSourceUrl 不能为空")
# 验证规则对象
self.validate_rule_search(book_source.get('ruleSearch', {}))
self.validate_rule_toc(book_source.get('ruleToc', {}))
self.validate_rule_content(book_source.get('ruleContent', {}))
return len(self.errors) == 0
def validate_rule_search(self, rule_search):
"""验证搜索规则"""
if not isinstance(rule_search, dict):
self.errors.append(f"ruleSearch 必须是对象类型")
return
# bookList 和 bookUrl 是必需的
if not rule_search.get('bookList'):
self.errors.append("ruleSearch.bookList 不能为空")
else:
print(f" bookList: {rule_search['bookList']}")
if not rule_search.get('bookUrl'):
self.errors.append("ruleSearch.bookUrl 不能为空")
else:
print(f" bookUrl: {rule_search['bookUrl']}")
if rule_search.get('name'):
print(f" name: {rule_search['name']}")
if rule_search.get('author'):
print(f" author: {rule_search['author']}")
if rule_search.get('coverUrl'):
print(f" coverUrl: {rule_search['coverUrl']}")
def validate_rule_toc(self, rule_toc):
"""验证目录规则"""
if not isinstance(rule_toc, dict):
self.errors.append(f"ruleToc 必须是对象类型")
return
# chapterList, chapterName, chapterUrl 是必需的
if not rule_toc.get('chapterList'):
self.errors.append("ruleToc.chapterList 不能为空")
if not rule_toc.get('chapterName'):
self.errors.append("ruleToc.chapterName 不能为空")
if not rule_toc.get('chapterUrl'):
self.errors.append("ruleToc.chapterUrl 不能为空")
def validate_rule_content(self, rule_content):
"""验证正文规则"""
if not isinstance(rule_content, dict):
self.errors.append(f"ruleContent 必须是对象类型")
return
# content 是必需的
if not rule_content.get('content'):
self.errors.append("ruleContent.content 不能为空")
def main():
"""主函数"""
if len(sys.argv) < 2:
print("使用方法: python validate_book_source.py <book_source.json>")
print("示例: python validate_book_source.py book_source.json")
sys.exit(1)
json_file = sys.argv[1]
validator = BookSourceValidator()
is_valid = validator.validate_json(json_file)
sys.exit(0 if is_valid else 1)
if __name__ == "__main__":
main()
drpy视频源创建与调试技能。当用户需要创建、修改、调试drpy视频源(用于TVBox、海阔视界、ZYPlayer等播放器)时使用此技能。包括drpy源属性配置、模板继承、正则表达式编写、本地代理设置、不同类型源(影视/听书/漫画/小说)支持。
---
name: drpy-source-creator
description: drpy视频源创建与调试技能。当用户需要创建、修改、调试drpy视频源(用于TVBox、海阔视界、ZYPlayer等播放器)时使用此技能。包括drpy源属性配置、模板继承、正则表达式编写、本地代理设置、不同类型源(影视/听书/漫画/小说)支持。
---
# drpy视频源创建与调试
## 概述
drpy源是一种用于TVBox、海阔视界、ZYPlayer等播放器的视频源格式,使用JavaScript编写,支持动态内容抓取和解析。本技能提供完整的drpy源创建指南,包含属性配置、模板继承、正则表达式编写、常见问题调试等。
## 快速开始
以下是基础的drpy源模板:
```javascript
var rule = {
// 影视|听书|漫画|小说
类型: '影视',
// 规则标题
title: '示例源',
// 网页的域名根
host: 'https://www.example.com',
// 网站的首页链接
homeUrl: '/latest/',
// 分类页面链接
url: '/fyclass/fypage.html',
// 搜索链接
searchUrl: '/vodsearch/紧箍咒/page/fypage.html',
// 是否启用全局搜索
searchable: 1,
// 是否启用快速搜索
quickSearch: 0,
// 是否启用筛选
filterable: 0,
// 请求头
headers: {
'User-Agent': 'MOBILE_UA',
},
// 请求超时(毫秒)
timeout: 5000,
// 是否需要调用免嗅lazy函数
play_parse: true,
// 静态分类名称
class_name: '电影&电视剧&动漫&综艺',
// 静态分类标识
class_url: '1&2&3&4',
}
```
## 实战案例分析:皮皮影视
### 分析步骤
**第一步:获取网站源码**
```python
import requests
headers = {'User-Agent': 'Mozilla/5.0...'}
resp = requests.get('https://www.pitv.cc/', headers=headers)
html = resp.text
```
**第二步:确认关键选择器**
```
✓ .hl-vod-list - 列表容器存在
✓ .hl-list-item - 列表项存在
✓ a&&title - 标题属性存在
✓ a&&data-original - 图片属性存在
✗ .remarks - 状态class不存在(实际是.hl-pic-text span)
```
**第三步:修正选择器**
```javascript
// 错误(参考代码)
推荐: '.hl-vod-list;li;a&&title;a&&data-original;.remarks&&Text;a&&href',
// 正确(实际HTML结构)
推荐: '.hl-vod-list;li;a&&title;a&&data-original;.hl-pic-text span&&Text;a&&href',
```
**第四步:验证搜索功能**
- 测试发现搜索需要验证码
- 结论:searchable应设为0
**第五步:最终可用配置**
```javascript
var rule = {
title: '皮皮影视',
host: 'https://www.pitv.cc',
url: '/show/fyclassfyfilter/page/fypage/',
searchable: 0, // 搜索需要验证码,禁用
class_name: '剧集&电影&动漫',
class_url: '1&2&3',
推荐: '.hl-vod-list;li;a&&title;a&&data-original;.hl-pic-text span&&Text;a&&href',
一级: '.hl-vod-list&&.hl-list-item;a&&title;a&&data-original;.hl-pic-text span&&Text;a&&href',
二级: {
title: 'h1&&Text',
img: '.hl-lazy&&data-original',
tabs: '.hl-plays-from&&a',
lists: '.hl-plays-list:eq(#id)&&a',
},
}
```
## drpy源属性详解
### 核心属性
| 属性 | 类型 | 说明 | 示例 |
|------|------|------|------|
| 类型 | string | 源类型:'影视'、'听书'、'漫画'、'小说' | '影视' |
| title | string | 规则标题,在播放器中显示的名称 | '鸭奈飞' |
| host | string | 网站域名根,包含http/https协议 | 'https://yanetflix.com' |
| homeUrl | string | 网站首页链接,用于获取分类和推荐 | '/latest/' |
| url | string | 分类页面链接模板 | '/vod/show/id/fyclass/page/fypage.html' |
| searchUrl | string | 搜索链接模板 | '/vodsearch/紧箍咒/page/fypage.html' |
| searchable | number | 是否启用全局搜索 | 1(启用)或0(禁用) |
| quickSearch | number | 是否启用快速搜索 | 1(启用)或0(禁用) |
| filterable | number | 是否启用筛选 | 1(启用)或0(禁用) |
### 分类相关属性
```javascript
// 静态分类配置
class_name: '电影&电视剧&动漫&综艺',
class_url: '1&2&3&4',
// 动态分类获取(推荐)
class_parse: '#side-menu:lt(1) li;a&&Text;a&&href;com/(.*?)/',
// 格式:列表选择器;标题选择器;链接选择器;正则提取(可选)
```
### 请求配置
```javascript
// 请求头配置
headers: {
'User-Agent': 'MOBILE_UA',
'Cookie': 'searchneed=ok',
'Referer': 'https://www.example.com/'
},
// 超时设置(毫秒)
timeout: 5000,
// 动态获取host(优先级最高)
hostJs: async function () {
let {HOST} = this;
return HOST
},
// 预处理(初始化时执行一次)
预处理: async function () {
let {HOST} = this;
return HOST
},
```
### 解析函数
drpy源的核心解析函数,用于获取不同页面的内容:
```javascript
// 推荐内容获取
推荐: async function () {
let {input} = this;
// input为homeUrl的内容
return []
},
// 一级列表获取
一级: async function () {
let {input} = this;
// input为url模板填入后的链接
return []
},
// 二级详情获取
二级: async function () {
let {input} = this;
// input为一级返回的链接
return {}
},
// 搜索内容获取
搜索: async function () {
let {input} = this;
// input为searchUrl模板填入后的链接
return []
},
```
### 播放相关
```javascript
// 免嗅播放解析
play_parse: true,
// lazy函数处理播放地址
lazy: async function () {
let {input} = this;
return {
url: input,
parse: 0
}
},
// 辅助嗅探规则
sniffer: 1,
isVideo: "http((?!http).){26,}\\.(m3u8|mp4|flv|avi|mkv|wmv|mpg|mpeg|mov|ts|3gp|rm|rmvb|asf|m4a|mp3|wma)",
```
## 模板继承
### 方法一:Object.assign
```javascript
var rule = Object.assign(muban.mxpro, {
title: '鸭奈飞',
host: 'https://yanetflix.com',
url: '/index.php/vod/show/id/fyclass/page/fypage.html',
class_parse: `.navbar-items li:gt(1):lt(6);a&&Text;a&&href;.*/(.*?).html`,
});
```
### 方法二:模板属性(新)
```javascript
var rule = {
title: 'cokemv',
模板: 'mxpro',
host: 'https://cokemv.me',
class_parse: `.navbar-items li:gt(1):lt(7);a&&Text;a&&href;/(\\d+).html`,
}
```
### 方法三:自动匹配模板
```javascript
var rule = {
模板: '自动',
模板修改: $js.toString(() => {
Object.assign(muban.自动.二级, {
tab_text: 'div--small&&Text',
});
}),
title: '剧圈圈[自动]',
host: 'https://www.jqqzx.cc/',
url: '/vodshow/id/fyclass/page/fypage.html',
searchUrl: '/vodsearch**/page/fypage.html',
class_parse: '.navbar-items li:gt(2):lt(8);a&&Text;a&&href;.*/(.*?)\.html',
cate_exclude: '今日更新|热榜',
}
```
## 不同类型源支持
### 影视源(默认)
标准的视频源,支持电影、电视剧、动漫等内容。
### 听书源
用法与影视源一致,播放音频内容。
### 漫画源
需要在`lazy`函数处理后返回`pics://`协议:
```javascript
lazy: async function () {
return "pics://图片链接1,图片链接2,图片链接3";
}
```
### 小说源
需要在`lazy`函数处理后返回`novel://`协议:
```javascript
lazy: async function () {
return 'novel://{"title":"章节名称","content":"章节内容"}';
}
```
## 正则表达式写法
在字符串属性(如`class_parse`)中使用正则表达式时需要注意转义:
| 需求 | JavaScript正则 | drpy字符串写法 |
|------|---------------|----------------|
| 数字 | `/(\d+)/` | `(\\d+)` |
| 任意字符 | `/.*/` | `.*` |
| 非贪婪匹配 | `/(.*?)/` | `(.*?)` |
| 多个分组 | `/(\d+)-(\w+)/` | `(\\d+)-(\\w+)` |
## 本地代理设置
```javascript
proxy_rule: `js:
log(input);
input = [200,'text;plain','hello drpy']
`,
```
本地代理的`input`参数格式为三元素数组:
`[status_code, content_type, data]`
示例:
```javascript
// 返回M3U8文件
input = [200,'application/vnd.apple.mpegurl', m3u8_content];
// 返回文本
input = [200,'text/plain', 'hello world'];
```
## 常见问题排查
### 1. 连接超时
- 检查`host`是否正确
- 调整`timeout`值(默认5000毫秒)
- 检查网络连接
### 2. 解析失败
- 确认选择器是否正确
- 使用`log()`函数调试输出
- 检查网页结构是否变化
### 3. 播放失败
- 检查`play_parse`设置
- 确认`lazy`函数返回正确格式
- 验证`sniffer`规则是否匹配视频链接
### 4. 分类获取失败
- 确认`class_parse`格式正确
- 检查正则表达式转义
- 验证分类页面是否可访问
## 工具和资源
### 代码格式化
```bash
# 安装uglify-js
npm install uglify-js -g
# 压缩JS文件
uglifyjs source.js -o source.min.js
```
### WebStorm配置
1. 获取uglifyjs路径:`where uglifyjs`
2. 配置外部工具:
- 程序: `C:\Users\username\AppData\Roaming\npm\uglifyjs.cmd`
- 参数: `$FileName$ -o $FileNameWithoutExtension$.min.js`
- 工作目录: `$FileDir$`
### 调试技巧
1. 使用`log()`函数输出调试信息
2. 分步测试各个解析函数
3. 验证模板继承是否正确
4. 检查属性拼写和格式
## 进阶功能
### RSA加解密
```javascript
// 加密
RSA.encode(data, key, option);
// 解密
RSA.decode(data, key, option);
```
### 动态线路管理
```javascript
// 线路顺序
tab_order: ['lzm3u8', 'wjm3u8', '1080zyk'],
// 线路名替换
tab_rename: {
'lzm3u8': '量子资源',
'1080zyk': '1080看',
},
// 移除线路
tab_remove: ['tkm3u8'],
```
### 图片处理
```javascript
// 图片替换
图片替换: 'https://old-domain.com/=>https://new-domain.com/',
// 图片来源(添加referer)
图片来源: '@Referer=http://www.example.com@User-Agent=custom-ua',
```
---
## Action与按钮
在drpy源的二级详情或其他交互页面中,可以通过返回`action`对象来定义按钮行为:
```javascript
二级: async function () {
return {
// 视频详情信息
vod_name: "影片名称",
vod_play_from: "播放来源",
vod_play_url: "第1集$播放地址",
// Action配置
action: {
button: 2, // 按钮类型:0-关闭, 1-取消, 2-确定和取消, 3-确定/取消/重置, 4-确定/取消/重置/预览
// 其他action属性...
},
};
},
```
### 按钮类型说明
| 值 | 按钮组合 | 适用场景 |
|----|----------|----------|
| 0 | 关闭按钮 | 只提供关闭功能 |
| 1 | 取消按钮 | 简单确认场景 |
| 2 | 确定和取消 | 需要用户确认的操作 |
| 3 | 确定、取消、重置 | 表单或设置页面 |
| 4 | 确定、取消、重置、预览 | 复杂的编辑页面 |
### 自定义Action
除了按钮,action还可以包含其他指令:
```javascript
action: {
button: 2,
message: "操作提示信息",
redirect: "目标URL",
// 其他自定义属性
},
```
## 工具与格式化
### 代码压缩
drpy源可以通过压缩减少文件大小:
```bash
# 安装uglify-js
npm install uglify-js -g
# 压缩JS文件
uglifyjs source.js -o source.min.js
# 推荐配置
uglifyjs source.js -o source.min.js -c -m --comments '/@license|@preserve/'
```
### WebStorm配置
1. 获取uglifyjs路径:`where uglifyjs`
2. 配置外部工具:
- 程序: `C:\Users\username\AppData\Roaming\npm\uglifyjs.cmd`
- 参数: `$FileName$ -o $FileNameWithoutExtension$.min.js`
- 工作目录: `$FileDir$`
详细格式化指南见:[formatting.md](references/formatting.md)
## 常见错误与排查
### 错误1:首页/分类空白(选择器不匹配)
**症状**:分类能显示,但点击后列表为空
**原因**:参考代码的选择器与实际HTML不符
**排查方法**:
```python
import requests
from bs4 import BeautifulSoup
headers = {'User-Agent': 'Mozilla/5.0...'}
html = requests.get('https://example.com/', headers=headers).text
soup = BeautifulSoup(html, 'html.parser')
# 检查选择器是否存在
print('hl-vod-list:', bool(soup.find(class_='hl-vod-list')))
print('hl-list-item:', bool(soup.find(class_='hl-list-item')))
print('remarks:', bool(soup.find(class_='remarks'))) # 可能不存在
# 查找实际的class名
for elem in soup.find_all(class_=True):
if 'pic' in str(elem.get('class')) or 'text' in str(elem.get('class')):
print(elem.name, elem.get('class'))
```
**解决方案**:
```javascript
// 错误
推荐: '.hl-vod-list;li;a&&title;a&&data-original;.remarks&&Text;a&&href',
// 正确(根据实际HTML调整)
推荐: '.hl-vod-list;li;a&&title;a&&data-original;.hl-pic-text span&&Text;a&&href',
```
### 错误2:搜索功能异常
**症状**:搜索返回空结果或报错
**排查方法**:
```python
# 测试搜索URL
search_resp = requests.get('https://example.com/search/关键词/', headers=headers)
print('状态码:', search_resp.status_code)
print('是否包含验证码:', '验证码' in search_resp.text)
print('是否包含结果:', 'hl-vod-list' in search_resp.text)
```
**常见情况**:
1. **需要验证码** → 设置 `searchable: 0` 禁用搜索
2. **URL格式错误** → 检查实际的搜索表单action
3. **需要登录/Cookie** → 添加Cookie到headers
### 错误3:二级详情页空白
**症状**:能进入详情页,但无播放列表
**排查方法**:
```python
# 获取详情页HTML
detail_html = requests.get('https://example.com/vod/123/', headers=headers).text
# 检查关键元素
print('h1:', 'h1' in detail_html)
print('hl-plays-list:', 'hl-plays-list' in detail_html)
print('hl-plays-from:', 'hl-plays-from' in detail_html)
```
**解决方案**:
```javascript
// 根据实际HTML调整二级选择器
二级: {
title: 'h1&&Text', // 使用h1而不是.hl-dc-title
img: '.hl-lazy&&data-original',
tabs: '.hl-plays-from&&a', // 播放源
lists: '.hl-plays-list:eq(#id)&&a', // 剧集列表
}
```
### 错误4:URL模板错误
**症状**:分类点击后跳转错误页面
**排查方法**:
```python
# 测试URL模板
# 参考代码: /show/fyclassfyfilter/page/fypage/
# 实际测试
for cls in ['1', '2', '3']:
url = f'https://example.com/show/{cls}--------/page/1/'
resp = requests.get(url, headers=headers)
print(f'{url} -> {resp.status_code}')
```
**解决方案**:
```javascript
// 确认URL格式
url: '/show/fyclassfyfilter/page/fypage/', // fyclass和fyfilter连在一起
// 或
url: '/type/fyclass/', // 简单的分类格式
```
## 调试技巧
### 1. 添加日志输出
```javascript
推荐: `js:
let html = request(input);
log('首页HTML长度:', html.length);
log('是否包含视频列表:', html.includes('hl-vod-list'));
let items = pdfa(html, '.hl-vod-list li');
log('提取到视频数量:', items.length);
// 继续解析...
`,
```
### 2. 使用极简版本测试
```javascript
// 先测试基础功能
var rule = {
title: '测试源',
host: 'https://example.com',
url: '/type/fyclass/',
class_name: '分类1&分类2',
class_url: '1&2',
推荐: 'a;a&&Text;;;a&&href', // 只取标题和链接
一级: 'a;a&&Text;;;a&&href',
二级: '*',
lazy: ''
};
```
### 3. 分步验证
1. **分类** → 是否能正常显示
2. **一级** → 点击分类后是否有列表
3. **二级** → 点击进入详情页是否正常
4. **播放** → 点击播放是否能正常解析
## 扩展阅读
### 详细参考资料
- **[属性详解](references/attributes.md)** - 所有属性的完整说明和示例
- **[模板继承](references/templates.md)** - 模板系统的详细使用方法
- **[解析函数](references/parsing.md)** - 选择器语法和解析函数编写指南
- **[问题排查](references/troubleshooting.md)** - 常见问题解决方案
- **[代码格式化](references/formatting.md)** - 代码压缩和格式化工具使用
### 实用模板文件
- **[基础模板](assets/basic_template.js)** - 适用于大多数CMS站的基础模板
- **[MXPro示例](assets/mxpro_example.js)** - MXPro模板继承完整示例
- **[皮皮影视实战](assets/pitv_example.js)** - 皮皮影视完整配置(含注释)
### 辅助脚本
- **[minify_drpy.js](scripts/minify_drpy.js)** - drpy源压缩脚本
- **[validate_drpy.js](scripts/validate_drpy.js)** - drpy源格式验证脚本
- **[analyze_site.py](scripts/analyze_site.py)** - 网站结构分析脚本
## 快速查找
| 任务 | 参考文档 |
|------|----------|
| 新建一个drpy源 | [基础模板](assets/basic_template.js) |
| 了解所有属性含义 | [属性详解](references/attributes.md) |
| 使用模板继承 | [模板继承](references/templates.md) |
| 编写解析函数 | [解析函数](references/parsing.md) |
| 调试源问题 | [问题排查](references/troubleshooting.md) |
| 压缩源代码 | [代码格式化](references/formatting.md) |
| 分析网站结构 | [analyze_site.py](scripts/analyze_site.py) |
**核心原则**:
1. **先验证,再编写** - 用Python获取HTML确认选择器存在
2. **从简到繁** - 先用极简版本测试,再逐步完善
3. **实际测试** - 不要完全相信参考代码,每个网站结构不同
4. **记录问题** - 遇到的选择器不匹配问题要记录,避免重复踩坑
**提示**:创建drpy源时,建议先使用简单的模板测试基础功能,再逐步添加高级功能。使用模板继承可以大大简化开发过程。
FILE:assets/basic_template.js
/**
* drpy基础模板
* 适用于大多数CMS影视站
*/
var rule = {
// 源类型:影视|听书|漫画|小说
类型: '影视',
// 源信息
title: '示例源',
编码: 'utf-8',
host: 'https://www.example.com',
// URL模板
homeUrl: '/',
url: '/vod/show/id/fyclass/page/fypage.html',
searchUrl: '/vodsearch/紧箍咒/page/fypage.html',
// 功能开关
searchable: 1,
quickSearch: 0,
filterable: 0,
// 请求配置
headers: {
'User-Agent': 'MOBILE_UA',
},
timeout: 5000,
// 播放配置
play_parse: true,
play_json: [{
re: '*',
json: {
jx: 1,
parse: 1,
},
}],
// 分类配置
class_name: '电影&电视剧&动漫&综艺',
class_url: '1&2&3&4',
// 或者使用动态分类获取
// class_parse: '.navbar li;a&&Text;a&&href;/(\\d+)/',
// 解析函数
推荐: async function () {
let {input} = this;
return [];
},
一级: async function () {
let {input} = this;
return [];
},
二级: async function () {
let {input} = this;
return {};
},
搜索: async function () {
let {input} = this;
return [];
},
// 免嗅解析
lazy: async function () {
let {input} = this;
return {
url: input,
parse: 0
};
},
};
/**
* 使用说明:
* 1. 修改host为实际域名
* 2. 修改url和searchUrl模板以匹配网站
* 3. 根据网站结构调整class_parse选择器
* 4. 实现解析函数或使用字符串简写格式
* 5. 测试所有功能
*/
FILE:assets/mxpro_example.js
/**
* MX影视Pro模板继承示例
* 适用于大多数CMS影视站
*/
// 方法1:Object.assign方式(传统)
var rule = Object.assign(muban.mxpro, {
title: '鸭奈飞',
host: 'https://yanetflix.com',
url: '/index.php/vod/show/id/fyclass/page/fypage.html',
class_parse: `.navbar-items li:gt(1):lt(6);a&&Text;a&&href;.*/(.*?).html`,
});
/**
* 方法2:模板属性方式(新)
*/
// var rule = {
// title: '鸭奈飞',
// 模板: 'mxpro',
// host: 'https://yanetflix.com',
// url: '/index.php/vod/show/id/fyclass/page/fypage.html',
// class_parse: `.navbar-items li:gt(1):lt(6);a&&Text;a&&href;.*/(.*?).html`,
// };
/**
* 方法3:自动匹配模板
*/
// var rule = {
// 模板: '自动',
// 模板修改: $js.toString(() => {
// // 可根据需要修改模板配置
// Object.assign(muban.自动.二级, {
// tab_text: 'div.small&&Text',
// });
// }),
// title: '示例源[自动]',
// host: 'https://www.example.com',
// url: '/vodshow/id/fyclass/page/fypage.html',
// searchUrl: '/vodsearch**/page/fypage.html',
// class_parse: '.navbar-items li:gt(2):lt(8);a&&Text;a&&href;.*/(.*?)\.html',
// };
/**
* 带详细注释的完整示例
*/
var rule_detailed = Object.assign(muban.mxpro, {
// 基本信息
title: '完整示例源',
编码: 'utf-8',
host: 'https://www.example.com',
// URL配置
homeUrl: '/',
url: '/vod/show/id/fyclass/page/fypage.html',
searchUrl: '/vodsearch/紧箍咒/page/fypage.html',
// 动态分类获取(覆盖模板的静态分类)
class_parse: '#side-menu li;a&&Text;a&&href;/(\\d+)/',
// 自定义headers(合并到模板headers中)
headers: {
'User-Agent': 'MOBILE_UA',
'Referer': 'https://www.example.com/',
},
// 自定义解析函数(可选)
推荐: async function () {
let html = await request(this.homeUrl);
// 自定义推荐解析逻辑
return [
{
vod_name: '自定义推荐影片',
vod_pic: 'https://example.com/pic.jpg',
vod_remarks: '更新至10集',
}
];
},
// 二级详情定制
二级: async function () {
// 先执行模板的二级函数
let baseResult = await muban.mxpro.二级.call(this);
// 添加自定义数据
baseResult.vod_content = baseResult.vod_content + '\n\n自定义内容';
return baseResult;
},
});
/**
* 使用注意事项:
* 1. 优先使用模板继承,减少重复代码
* 2. 只覆盖需要修改的属性,保留模板默认配置
* 3. 测试时先验证模板本身是否适合目标网站
* 4. 注意属性覆盖顺序,后定义的覆盖先定义的
*/
FILE:assets/pitv_example.js
/**
* 皮皮影视实战示例
* 基于实际HTML结构分析的配置
*
* 分析过程:
* 1. 获取首页HTML: requests.get('https://www.pitv.cc/')
* 2. 确认选择器:
* - .hl-vod-list ✓ 存在
* - .hl-list-item ✓ 存在
* - a&&title ✓ 存在
* - a&&data-original ✓ 存在
* - .remarks ✗ 不存在(实际是.hl-pic-text span)
* 3. 测试搜索: 需要验证码,禁用搜索功能
* 4. 验证详情页: .hl-plays-list和.hl-plays-from存在
*/
var rule = {
// 基本信息
title: '皮皮影视',
host: 'https://www.pitv.cc',
// URL模板 - 支持筛选参数
url: '/show/fyclassfyfilter/page/fypage/',
filter_url: '{{fl.class}}{{fl.area}}{{fl.year}}{{fl.isend}}{{fl.letter}}',
// 搜索功能需要验证码,禁用
searchable: 0,
quickSearch: 0,
filterable: 1,
// 请求头
headers: {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36',
'Referer': 'https://www.pitv.cc/'
},
timeout: 5000,
limit: 40,
// 播放配置
play_parse: true,
double: true,
cate_exclude: '明星|专题|排行',
// 动态分类和筛选
class_parse: $js.toString(() => {
try {
let classes = [
{ type_id: '1', type_name: '剧集' },
{ type_id: '2', type_name: '电影' },
{ type_id: '3', type_name: '动漫' }
];
let filterObj = {};
classes.forEach(cls => {
let html = request(rule.host + '/show/' + cls.type_id + '/', { headers: rule.headers });
if (!html) return;
const parseFilter = (label, key) => {
let opts = [{ n: '全部', v: '' }];
let bReg = new RegExp(`<span>label<\/span>[\\s\\S]*?<ul[^>]*>([\\s\\S]*?)<\/ul>`);
let bMatch = html.match(bReg);
if (bMatch) {
let iReg = new RegExp(`\/key\/([^/]+)\/">([^<]+)<\/a>`, 'g');
let im, seen = new Set();
while ((im = iReg.exec(bMatch[1])) !== null) {
let v = im[1], n = im[2].replace(/<.*?>/g, "").trim();
if (n !== '全部' && !seen.has(v)) {
opts.push({ n: n, v: '/' + key + '/' + v });
seen.add(v);
}
}
}
return opts;
};
let currentFilters = [
{ key: 'class', name: '类型', value: parseFilter('类型', 'class') },
{ key: 'area', name: '地区', value: parseFilter('地区', 'area') },
{ key: 'year', name: '年份', value: parseFilter('年份', 'year') },
{ key: 'isend', name: '状态', value: parseFilter('状态', 'isend') },
{ key: 'letter', name: '字母', value: parseFilter('字母', 'letter') }
].filter(f => f.value.length > 1);
filterObj[cls.type_id] = currentFilters;
});
input = classes;
homeObj.filter = filterObj;
} catch (e) {
// 备用静态分类
input = [
{ type_id: '1', type_name: '剧集' },
{ type_id: '2', type_name: '电影' },
{ type_id: '3', type_name: '动漫' }
];
}
}),
// 播放器解析
lazy: $js.toString(() => {
let html = request(input);
let playerMatch = html.match(/var player_.*?=({.*?})/);
if (playerMatch) {
let player = JSON.parse(playerMatch[1]);
let url = unescape(player.url);
let from = player.from;
let playerJs = request(HOST + '/static/player/' + from + '.js');
let parseApi = '';
let apiMatch = playerJs.match(/src="([^"]+)"/);
if (apiMatch) {
parseApi = apiMatch[1];
} else {
parseApi = 'https://api.apiimg.com/show/super.php?id=';
}
if (parseApi.startsWith('//')) {
parseApi = 'https:' + parseApi;
}
let finalUrl = parseApi + url;
if (player.link_next) {
let nextPath = player.link_next.startsWith('http') ? player.link_next : HOST + player.link_next;
finalUrl += '&next=' + nextPath;
}
input = {
jx: 0,
url: finalUrl,
parse: 1,
header: rule.headers
};
}
}),
// 推荐 - 热播剧集
// 关键修正:.remarks改为.hl-pic-text span
推荐: '.hl-vod-list;li;a&&title;a&&data-original;.hl-pic-text span&&Text;a&&href',
// 一级 - 分类列表
// 使用&&连接容器和列表项
一级: '.hl-vod-list&&.hl-list-item;a&&title;a&&data-original;.hl-pic-text span&&Text;a&&href',
// 二级 - 详情页
// 关键修正:使用实际存在的class
二级: {
title: 'h1&&Text', // 标题在h1中
img: '.hl-lazy&&data-original', // 图片
desc: '.hl-vod-content&&Text', // 简介
content: '.hl-conch-text&&Text', // 详细内容
tabs: '.hl-plays-from&&a', // 播放源
tab_text: 'a&&Text',
lists: '.hl-plays-list:eq(#id)&&a', // 剧集列表
},
// 搜索 - 禁用(需要验证码)
搜索: '',
};
/**
* 常见错误对照表:
*
* 错误选择器(参考代码) 正确选择器(实际HTML)
* ------------------------- ------------------------
* .remarks&&Text .hl-pic-text span&&Text
* .hl-dc-title&&Text h1&&Text
* .hl-dc-content&&Text .hl-vod-content&&Text
* .hl-content-text&&Text .hl-conch-text&&Text
* .hl-tabs&&a .hl-plays-from&&a
*
* 教训:不要直接复制参考代码的选择器,必须根据实际HTML结构调整!
*/
FILE:README.md
# drpy视频源创建技能
## 简介
这是一个专门用于创建、调试和优化drpy视频源的OpenClaw技能。drpy源是一种用于TVBox、海阔视界、ZYPlayer等播放器的视频源格式,使用JavaScript编写,支持动态内容抓取和解析。
## 技能特点
### 🎯 实战导向
- 基于真实网站分析(皮皮影视案例)
- 提供完整的分析流程和调试方法
- 总结常见错误和解决方案
### 📚 完整文档
- **SKILL.md** - 主要技能文档,包含快速开始和实战案例
- **references/** - 详细参考文档(属性、模板、解析、排查、格式化)
- **assets/** - 实用模板文件(基础模板、MXPro示例、皮皮影视实战)
- **scripts/** - 辅助工具脚本(压缩、验证、分析)
### 🔧 实用工具
- **analyze_site.py** - 网站结构分析工具
- **validate_drpy.js** - 源格式验证工具
- **minify_drpy.js** - 代码压缩工具
## 快速开始
### 1. 分析目标网站
```bash
python scripts/analyze_site.py https://www.example.com
```
### 2. 使用基础模板
参考 `assets/basic_template.js` 创建新源
### 3. 验证源格式
```bash
node scripts/validate_drpy.js your-source.js
```
### 4. 压缩代码
```bash
node scripts/minify_drpy.js your-source.js
```
## 核心原则
### ✅ 正确的工作流程
1. **分析** - 用Python获取HTML,确认选择器存在
2. **编写** - 基于实际HTML结构编写选择器
3. **测试** - 从简到繁,逐步验证功能
4. **优化** - 添加筛选、搜索等高级功能
### ❌ 常见错误
- 直接复制参考代码,不验证选择器
- 假设所有网站结构相同
- 忽略搜索功能的验证码限制
- 使用错误的URL模板格式
## 文件结构
```
drpy-source-creator/
├── SKILL.md # 主要技能文档
├── README.md # 本文件
├── references/
│ ├── attributes.md # 属性详解
│ ├── templates.md # 模板继承
│ ├── parsing.md # 解析函数
│ ├── troubleshooting.md # 问题排查
│ └── formatting.md # 代码格式化
├── assets/
│ ├── basic_template.js # 基础模板
│ ├── mxpro_example.js # MXPro示例
│ └── pitv_example.js # 皮皮影视实战
└── scripts/
├── analyze_site.py # 网站分析工具
├── validate_drpy.js # 格式验证工具
└── minify_drpy.js # 代码压缩工具
```
## 实战案例:皮皮影视
### 分析结果
- **网站**: https://www.pitv.cc
- **模板**: 海螺模板(hl-1)
- **关键选择器**:
- 列表容器: `.hl-vod-list`
- 列表项: `.hl-list-item`
- 图片: `img&&data-original`
- 状态: `.hl-pic-text span` (不是.remarks)
- **搜索**: 需要验证码,禁用
### 配置要点
```javascript
var rule = {
title: '皮皮影视',
host: 'https://www.pitv.cc',
url: '/show/fyclassfyfilter/page/fypage/',
searchable: 0, // 搜索需要验证码
// 关键修正:.remarks改为.hl-pic-text span
推荐: '.hl-vod-list;li;a&&title;a&&data-original;.hl-pic-text span&&Text;a&&href',
一级: '.hl-vod-list&&.hl-list-item;a&&title;a&&data-original;.hl-pic-text span&&Text;a&&href',
// 二级选择器使用实际存在的class
二级: {
title: 'h1&&Text',
img: '.hl-lazy&&data-original',
tabs: '.hl-plays-from&&a',
lists: '.hl-plays-list:eq(#id)&&a',
},
}
```
## 学习路径
### 初学者
1. 阅读 `SKILL.md` 快速开始部分
2. 使用 `assets/basic_template.js` 创建简单源
3. 运行 `scripts/analyze_site.py` 分析目标网站
4. 参考 `assets/pitv_example.js` 了解实战案例
### 进阶用户
1. 阅读 `references/parsing.md` 学习高级解析技巧
2. 使用 `references/templates.md` 掌握模板继承
3. 参考 `references/troubleshooting.md` 解决复杂问题
4. 使用 `scripts/validate_drpy.js` 验证源质量
## 更新日志
### v1.0 (2026-03-17)
- 初始版本
- 添加皮皮影视实战案例
- 创建网站分析工具
- 完善问题排查文档
## 贡献
欢迎提交Issue和PR,帮助改进这个技能。
## 许可证
MIT License
FILE:references/attributes.md
# drpy源属性详解
## 基础属性
### 类型配置
| 属性 | 类型 | 说明 | 示例 |
|------|------|------|------|
| 类型 | string | 源类型:'影视'、'听书'、'漫画'、'小说' | '影视' |
| title | string | 规则标题,播放器中显示的名称 | '鸭奈飞' |
| 编码 | string | 网页编码,默认utf-8 | 'utf-8' |
| 搜索编码 | string | 搜索独立编码,优先于全局编码 | 'gbk' |
### 域名配置
| 属性 | 类型 | 说明 | 示例 |
|------|------|------|------|
| host | string | 网页域名根,包含协议头 | 'https://www.baidu.com' |
| hostJs | string/function | 动态抓取域名的JS代码 | `print(HOST);let html=request(HOST);...` |
| homeUrl | string | 网站首页链接 | '/latest/' |
### URL模板
| 属性 | 类型 | 说明 | 示例 |
|------|------|------|------|
| url | string | 分类页面链接模板 | '/fyclass/fypage.html[/fyclass/]' |
| detailUrl | string | 二级详情拼接链接 | 'https://yanetflix.com/voddetail/fyid.html' |
| searchUrl | string | 搜索链接模板 | '/vodsearch/紧箍咒/page/fypage.html' |
### 功能开关
| 属性 | 类型 | 说明 | 示例 |
|------|------|------|------|
| searchable | number | 是否启用全局搜索 | 1(启用)或0(禁用) |
| quickSearch | number | 是否启用快速搜索 | 1(启用)或0(禁用) |
| filterable | number | 是否启用筛选 | 1(启用)或0(禁用) |
| double | boolean | 是否双层列表定位 | true(双层)或false(单层) |
## 分类相关属性
### 静态分类
```javascript
// 静态分类名称(用&分隔)
class_name: '电影&电视剧&动漫&综艺',
// 静态分类标识(与名称对应)
class_url: '1&2&3&4',
```
### 动态分类
```javascript
// 动态分类获取
class_parse: '#side-menu:lt(1) li;a&&Text;a&&href;com/(.*?)/',
// 格式:列表选择器;标题选择器;链接选择器;正则提取(可选)
// 排除某些分类
cate_exclude: '今日更新|热榜',
```
## 请求配置
### 请求头
```javascript
headers: {
// 常用User-Agent
'User-Agent': 'MOBILE_UA', // 移动端UA
'User-Agent': 'PC_UA', // PC端UA
'User-Agent': 'okhttp/3.14.9', // 常用Android UA
// 其他常见头
'Cookie': 'searchneed=ok',
'Referer': 'https://www.example.com/',
'Accept': 'application/json, text/javascript, */*; q=0.01',
'Accept-Language': 'zh-CN,zh;q=0.9',
'Accept-Encoding': 'gzip, deflate, br',
'Cache-Control': 'no-cache',
'X-Requested-With': 'XMLHttpRequest',
},
```
### 超时和重试
```javascript
// 请求超时(毫秒)
timeout: 5000, // 默认3000,建议5000
// 服务器解析播放
play_parse: true,
// play_json配置
play_json: [{
re: '*',
json: {
jx: 1,
parse: 1,
},
}],
```
## 线路和标签管理
### 线路过滤
```javascript
// 排除某些线路名
tab_exclude: '',
// 移除某个线路及相关选集
tab_remove: ['tkm3u8'],
// 线路顺序(按优先级排序)
tab_order: ['lzm3u8', 'wjm3u8', '1080zyk', 'zuidam3u8', 'snm3u8'],
// 线路名替换(显示名称美化)
tab_rename: {
'lzm3u8': '量子',
'1080zyk': '1080看',
'zuidam3u8': '最大资源',
'kuaikan': '快看',
'bfzym3u8': '暴风',
'ffm3u8': '非凡',
'snm3u8': '索尼',
'tpm3u8': '淘片',
'tkm3u8': '天空',
},
```
## 筛选功能
### 筛选配置
```javascript
// 筛选条件字典
filter: {
style: {
name: '类型',
value: [
{n: '全部', v: ''},
{n: '爱情', v: '爱情'},
{n: '喜剧', v: '喜剧'},
]
},
zone: {
name: '地区',
value: [
{n: '全部', v: ''},
{n: '大陆', v: '大陆'},
{n: '香港', v: '香港'},
]
}
},
// 默认筛选条件(不同分类不同默认值)
filter_def: {
douyu: {
area: '一起看',
other: '..'
},
huya: {
area: '影音馆',
other: '..'
}
},
// 筛选传参模板
filter_url: 'style={{fl.style}}&zone={{fl.zone}}&year={{fl.year}}&fee={{fl.fee}}&order={{fl.order}}',
```
## 显示和样式
### 列表样式
```javascript
// 海阔一级列表样式
hikerListCol: "avatar", // 列表显示样式
// 可选值: "avatar"(头像模式), ""(默认列表)
// 海阔推荐列表样式
hikerClassListCol: "avatar",
// 首页推荐显示数量
limit: 6,
// 分页控制
pagecount: {"1": 1, "2": 1, "3": 1, "4": 1, "5": 1, "7": 1, "时间表": 1},
```
### 图片处理
```javascript
// 图片来源(添加referer和UA)
图片来源: '@Referer=http://www.jianpianapp.com@User-Agent=jianpian-version350',
// 图片链接替换
图片替换: 'https://www.keke6.app/=>https://vres.a357899.cn/',
```
## JavaScript函数属性
### 动态处理函数
```javascript
// 动态域名获取(优先级最高)
hostJs: async function () {
let {HOST} = this;
return HOST
},
// 预处理函数(源初始化时执行一次)
预处理: async function () {
let {HOST} = this;
return HOST
},
// 二级访问前处理
二级访问前: `js:
log(MY_URL);
let jump=request(MY_URL).match(/href="(.*?)"/)[1];
log(jump);
MY_URL=urljoin2(MY_URL,jump)
`,
```
### 解析函数返回值格式
#### 推荐/一级/搜索函数
返回数组,每个元素包含:
```javascript
{
vod_id: "123", // 视频ID
vod_name: "影片名称", // 视频名称
vod_pic: "封面图URL", // 封面图
vod_remarks: "更新状态", // 备注/状态
vod_year: "2024", // 年份
vod_area: "大陆", // 地区
vod_actor: "演员", // 主演
vod_director: "导演", // 导演
vod_content: "简介", // 剧情简介
}
```
#### 二级函数
返回对象包含:
```javascript
{
vod_name: "影片名称",
vod_pic: "封面图URL",
vod_remarks: "更新状态",
vod_year: "2024",
vod_area: "大陆",
vod_actor: "演员",
vod_director: "导演",
vod_content: "简介",
vod_play_from: "播放来源", // 如:量子资源
vod_play_url: "播放链接", // 如:第1集$播放地址
}
```
## 特殊功能属性
### 本地代理
```javascript
proxy_rule: `js:
log(input);
input = [200,'text;plain','hello drpy']
`,
```
### 辅助嗅探
```javascript
// 是否启用辅助嗅探
sniffer: 1,
// 视频链接正则
isVideo: "http((?!http).){26,}\\.(m3u8|mp4|flv|avi|mkv|wmv|mpg|mpeg|mov|ts|3gp|rm|rmvb|asf|m4a|mp3|wma)",
// 自定义嗅探规则
isVideo: `js:
log(input);
if(/m3u8/.test(input)){
input = true
}else{
input = false
}
`,
```
### 免嗅解析
```javascript
// lazy函数处理播放地址
lazy: async function () {
let {input} = this;
return {
url: input,
parse: 0
}
},
```
## 注意事项
1. **属性命名**:使用小写或驼峰命名,如`class_name`、`play_parse`
2. **编码设置**:默认UTF-8,GBK网站需要指定`编码`或`搜索编码`
3. **URL模板**:使用`fyclass`、`fypage`、`**`、`fyid`等占位符
4. **正则转义**:字符串中的正则需要双重转义,如`(\\d+)`对应`/(\d+)/`
5. **缓存问题**:修改JS后需要重新加载源配置(猫影视需换源)
FILE:references/formatting.md
# drpy源代码格式化与压缩
## 为什么要格式化代码
1. **减小文件体积**:压缩后的源文件加载更快
2. **统一代码风格**:便于维护和共享
3. **去除冗余信息**:移除注释和空白字符
4. **保护代码**:混淆变量名增加安全性
## 工具选择
### uglify-js(推荐)
最常用的JavaScript压缩工具,支持丰富选项。
### terser
uglify-es的替代品,支持ES6+。
### 在线工具
适用于快速测试和小文件处理。
## uglify-js使用指南
### 安装
```bash
# 全局安装
npm install uglify-js -g
# 查看版本
uglifyjs --version
```
### 基本压缩
```bash
# 压缩单个文件
uglifyjs source.js -o source.min.js
# 压缩并混淆变量名
uglifyjs source.js -o source.min.js --mangle
# 压缩、混淆并启用高级压缩
uglifyjs source.js -o source.min.js -c -m
```
### 常用选项
| 选项 | 简写 | 说明 |
|------|------|------|
| `--compress` | `-c` | 启用代码压缩 |
| `--mangle` | `-m` | 混淆变量名 |
| `--output <file>` | `-o` | 指定输出文件 |
| `--beautify` | `-b` | 美化输出(与压缩相反) |
| `--comments` | | 保留特定注释 |
| `--source-map` | | 生成source map |
### 完整压缩命令
```bash
# 推荐配置
uglifyjs drpy-source.js -o drpy-source.min.js \
--compress \
--mangle \
--comments '/@license|@preserve/' \
--source-map "url='drpy-source.min.js.map',includeSources"
```
## WebStorm集成配置
### 步骤1:找到uglifyjs路径
```bash
# Windows CMD
where uglifyjs
# Windows PowerShell
Get-Command uglifyjs | Select-Object -ExpandProperty Source
# 结果示例
C:\Users\用户名\AppData\Roaming\npm\uglifyjs.cmd
```
### 步骤2:配置外部工具
1. **文件 → 设置 → 工具 → 外部工具**
2. **点击"+"添加新工具**
3. 配置如下:
```
名称: UglifyJS
程序: C:\Users\用户名\AppData\Roaming\npm\uglifyjs.cmd
实参: $FileName$ -o $FileNameWithoutExtension$.min.js
工作目录: $FileDir$
```
### 步骤3:使用快捷键
- 选中JS文件
- 右键 → 外部工具 → UglifyJS
- 或配置键盘快捷键
## 手动格式化技巧
### 删除注释
```javascript
// 开发时的详细注释
var rule = {
// 这里是类型定义
类型: '影视', // 影视源
};
```
压缩后:
```javascript
var rule={类型:"影视"};
```
### 简化空格和换行
```javascript
// 原始代码
var rule = {
title : '示例源',
host : 'https://example.com'
};
// 压缩后
var rule={title:"示例源",host:"https://example.com"};
```
### 函数简化
```javascript
// 原始函数
推荐: async function () {
let {input} = this;
return [];
},
// 压缩后(匿名函数)
推荐:async function(){return[]},
```
## drpy特定压缩注意事项
### 保留必要属性名
drpy中的中文属性名必须保留:
```javascript
// 不要混淆这些属性名
属性名保留列表: [
'类型', '标题', 'host', 'url', 'searchUrl',
'class_name', 'class_url', 'play_parse',
'推荐', '一级', '二级', '搜索', 'lazy'
]
```
### 配置示例
```bash
# 使用--mangle-props保留重要属性
uglifyjs source.js -o source.min.js -m reserved=['类型','标题','host']
```
### 模板继承代码
模板继承代码可能有特殊结构,压缩时需要测试:
```javascript
// 压缩前
var rule = Object.assign(muban.mxpro, {
title: '示例源',
host: 'https://example.com',
});
// 压缩后应保持结构完整
var rule=Object.assign(muban.mxpro,{title:"示例源",host:"https://example.com"});
```
## 压缩前后对比
### 压缩前示例
```javascript
/**
* 鸭奈飞影视源
* 最后更新: 2024-01-01
*/
var rule = {
// 源类型
类型: '影视',
// 源信息
title: '鸭奈飞',
// 域名
host: 'https://yanetflix.com',
// 首页
homeUrl: '/',
// 分类页模板
url: '/vod/show/id/fyclass/page/fypage.html',
// 搜索页模板
searchUrl: '/vodsearch/紧箍咒/page/fypage.html',
// 函数定义
推荐: async function () {
let {input} = this;
return [];
},
};
```
### 压缩后示例
```javascript
var rule={类型:"影视",title:"鸭奈飞",host:"https://yanetflix.com",homeUrl:"/",url:"/vod/show/id/fyclass/page/fypage.html",searchUrl:"/vodsearch/紧箍咒/page/fypage.html",推荐:async function(){return[]}};
```
**文件大小对比**:
- 原始文件: 约 500 字节
- 压缩文件: 约 200 字节
- 压缩率: 约 60%
## 高级压缩策略
### 分阶段压缩
```bash
# 阶段1:基础压缩
uglifyjs source.js -o stage1.js -c
# 阶段2:混淆变量
uglifyjs stage1.js -o stage2.js -m
# 阶段3:进一步优化
uglifyjs stage2.js -o final.min.js -c passes=2
```
### 批量压缩脚本
创建`batch_minify.bat`(Windows):
```batch
@echo off
echo 开始压缩drpy源文件...
for %%f in (*.js) do (
if not "%%f" == "*.min.js" (
echo 压缩 %%f ...
uglifyjs "%%f" -o "%%~nf.min.js" -c -m
)
)
echo 压缩完成!
pause
```
或`batch_minify.sh`(Linux/macOS):
```bash
#!/bin/bash
echo "开始压缩drpy源文件..."
for file in *.js; do
if [[ $file != *.min.js ]]; then
echo "压缩 $file ..."
uglifyjs "$file" -o "file%.js.min.js" -c -m
fi
done
echo "压缩完成!"
```
## 常见问题
### Q: 压缩后源不工作
A:
1. 检查是否混淆了drpy必需的属性名
2. 尝试不使用`--mangle`选项
3. 分段压缩,定位问题步骤
4. 保留开发版本用于调试
### Q: 压缩后的代码无法阅读
A:
1. 保留source map文件
2. 开发时使用未压缩版本
3. 使用`--beautify`选项生成格式化的压缩版
### Q: uglifyjs命令不存在
A:
1. 确认已全局安装:`npm install uglify-js -g`
2. 检查PATH环境变量包含npm全局目录
3. Windows用户可能需要重启终端
### Q: 压缩效率不满意
A:
1. 手动删除不必要的注释和空格
2. 简化代码结构
3. 使用更激进的压缩选项:`-c unsafe=true`
## 最佳实践
1. **保留开发版本**:始终保留未压缩的源代码
2. **版本控制**:将.min.js文件加入.gitignore
3. **测试验证**:压缩后务必测试功能是否正常
4. **逐步压缩**:先压缩再混淆,便于问题定位
5. **备份重要注释**:使用`/*! 重要注释 */`格式
## 替代方案
### 在线压缩工具
- [JavaScript Minifier](https://javascript-minifier.com/)
- [UglifyJS Online](https://skalman.github.io/UglifyJS-online/)
### 使用构建工具
Webpack、Rollup等构建工具集成压缩功能。
### 编辑器插件
VS Code、WebStorm等编辑器有代码格式化插件。
FILE:references/parsing.md
# drpy解析函数与选择器指南
## 解析函数概述
drpy的核心是四个解析函数,分别处理不同页面的内容解析:
1. **推荐** - 首页推荐内容
2. **一级** - 分类列表内容
3. **二级** - 视频详情页面
4. **搜索** - 搜索结果页面
## 函数基本结构
### 标准异步函数格式
```javascript
// 异步函数格式(推荐)
推荐: async function () {
let {input} = this;
// input是homeUrl获取的页面内容
// 返回推荐视频数组
return [];
},
一级: async function () {
let {input} = this;
// input是url模板填入后的分类页面链接
// 返回分类视频列表数组
return [];
},
二级: async function () {
let {input} = this;
// input是一级返回的详情页链接
// 返回详情对象
return {};
},
搜索: async function () {
let {input} = this;
// input是searchUrl模板填入后的搜索页面链接
// 返回搜索结果数组
return [];
},
```
### 简写字符串格式
对于简单的选择器解析,可以使用分号分隔的字符串格式:
```javascript
一级: '.video-list li;h3&&Text;img&&data-src;.time&&Text;a&&href',
// 格式:列表选择器;标题;图片;描述;链接
```
### 字符串格式详细说明
```
推荐/一级/搜索字符串格式:
列表选择器;标题选择器;图片选择器;描述选择器;链接选择器;详情选择器(可选)
示例:
'.col-md-3;.title&&Text;img&&src;.actor&&Text;a&&href'
```
## 选择器语法
### 基础选择器
| 选择器 | 说明 | 示例 |
|--------|------|------|
| `tag.class` | 选择class为.class的tag元素 | `div.item` |
| `#id` | 选择id为#id的元素 | `#video-list` |
| `tag[attr=value]` | 选择属性匹配的元素 | `a[href*=play]` |
| `tag1 tag2` | 后代选择器 | `div.video-list li` |
| `tag1>tag2` | 子元素选择器 | `ul>li` |
### 特殊选择器
| 选择器 | 说明 | 示例 |
|--------|------|------|
| `&&` | 分隔多个选择器,依次尝试 | `img&&data-src&&src` |
| `:eq(n)` | 选择第n个元素(从0开始) | `li:eq(0)` |
| `:lt(n)` | 选择前n个元素 | `li:lt(5)` |
| `:gt(n)` | 选择第n个之后的元素 | `li:gt(2)` |
| `:contains(text)` | 包含指定文本的元素 | `span:contains("更新")` |
| `:not(selector)` | 排除匹配的元素 | `li:not(.ads)` |
### 属性提取
| 语法 | 说明 | 示例 | 获取结果 |
|------|------|------|----------|
| `tag&&Text` | 获取元素的文本内容 | `h3&&Text` | "影片标题" |
| `tag&&href` | 获取href属性 | `a&&href` | "/vod/123.html" |
| `tag&&data-src` | 获取data-src属性 | `img&&data-src` | "图片URL" |
| `tag&&src` | 获取src属性 | `img&&src` | "图片URL" |
| `tag&&html` | 获取HTML内容 | `div&&html` | "<span>内容</span>" |
## 各函数详细解析
### 推荐函数
**作用**:解析首页推荐内容
**输入**:`input`为`homeUrl`获取的HTML内容
**返回格式**:视频对象数组
```javascript
推荐: async function () {
let html = await request(this.homeUrl);
return [
{
vod_id: "123", // 视频ID(可选)
vod_name: "影片名称", // 必需:视频名称
vod_pic: "封面图.jpg", // 必需:封面图URL
vod_remarks: "更新至10集", // 可选:备注/状态
vod_year: "2024", // 可选:年份
vod_area: "大陆", // 可选:地区
vod_actor: "演员列表", // 可选:主演
vod_director: "导演", // 可选:导演
vod_content: "剧情简介", // 可选:简介
},
// ...更多视频
];
},
```
**字符串简写示例**:
```javascript
推荐: '.video-item;.title&&Text;img&&src;.info&&Text;a&&href',
```
### 一级函数
**作用**:解析分类列表页面
**输入**:`input`为分类URL(已替换`fyclass`、`fypage`)
**返回格式**:视频对象数组(同推荐函数)
```javascript
一级: async function () {
let html = await request(this.input);
const videos = [];
// 使用cheerio之类的库解析html
// 这里省略具体解析代码
return videos;
},
```
**字符串简写示例**:
```javascript
一级: '.col-sm-6;h3&&Text;img&&data-src;.date&&Text;a&&href',
```
### 二级函数
**作用**:解析视频详情页,获取播放列表
**输入**:`input`为视频详情页URL
**返回格式**:详情对象
```javascript
二级: async function () {
let html = await request(this.input);
return {
// 基本信息
vod_name: "影片名称",
vod_pic: "封面图.jpg",
vod_remarks: "更新至10集",
vod_year: "2024",
vod_area: "大陆",
vod_actor: "张三,李四",
vod_director: "王五",
vod_content: "剧情简介...",
// 播放信息(关键!)
vod_play_from: "播放来源1$$播放来源2", // 多个来源用$$分隔
vod_play_url: "第1集$播放地址1#第2集$播放地址2$$第1集$播放地址3#第2集$播放地址4",
// 格式说明:
// vod_play_from: "来源1$$来源2$$来源3"
// vod_play_url: "集数1$地址1#集数2$地址2$$集数1$地址3#集数2$地址4"
// $$分隔不同来源,#分隔同一来源的不同集数,$分隔集数和地址
};
},
```
**特殊返回值**:
```javascript
// 无二级详情,直接跳到播放
二级: '*',
// 简写格式(同海阔dr二级)
二级: {
title: 'h1&&Text',
img: '.poster&&src',
desc: '.info&&Text',
content: '.intro&&Text',
tabs: '.play-source&&Text',
lists: '.play-list-item&&Text',
tab_text: 'body&&Text', // 选项卡文本选择器
list_text: 'body&&Text', // 播放列表文本选择器
list_url: 'a&&href', // 播放链接选择器
},
```
### 搜索函数
**作用**:解析搜索结果页面
**输入**:`input`为搜索URL(已替换`**`、`fypage`)
**返回格式**:视频对象数组(同推荐函数)
```javascript
搜索: async function () {
return [];
},
// 简写格式
搜索: '.search-item;.title&&Text;img&&src;.info&&Text;a&&href',
```
**集成一级解析**:
```javascript
搜索: '*', // 直接使用一级的解析逻辑
```
## 高级解析技巧
### 使用CSS选择器组合
```javascript
一级: async function () {
let $ = cheerio.load(html);
let videos = [];
$('.video-list > li:not(.ad)').each((i, elem) => {
videos.push({
vod_name: $(elem).find('h3.title').text().trim(),
vod_pic: $(elem).find('img[data-src]').attr('data-src') || $(elem).find('img').attr('src'),
vod_remarks: $(elem).find('.update').text().trim(),
vod_id: $(elem).find('a').attr('href').match(/\/(\d+)\.html/)[1],
});
});
return videos;
},
```
### 多级选择器
```javascript
// 复杂的多级选择
二级: {
title: '.video-info h1&&Text',
img: '.poster-wrapper img&&src',
desc: '.video-data&&Text',
content: '.intro-content&&Text',
tabs: 'ul.play-tab li&&Text',
lists: '.play-list li&&Text',
// 更精确的选择
tab_text: 'ul.play-tab li a&&Text',
list_text: '.play-list li a&&Text',
list_url: '.play-list li a&&data-url',
},
```
### 正则表达式提取
```javascript
class_parse: '#nav li;a&&Text;a&&href;vod/(\\d+)/',
一级: async function () {
let videos = [];
// 使用正则提取
let pattern = /<a href="(\/vod\/\d+\.html)" title="([^"]+)"/g;
let match;
while ((match = pattern.exec(html)) !== null) {
videos.push({
vod_name: match[2],
vod_id: match[1].match(/\/(\d+)\.html/)[1],
vod_pic: '', // 可能需要另外提取
vod_remarks: '',
});
}
return videos;
},
```
### 动态内容处理
```javascript
二级: async function () {
// 处理动态加载的内容
let html = await request(this.input);
// 检查是否有AJAX加载的内容
if (html.includes('data-player=')) {
let playerData = html.match(/data-player="([^"]+)"/);
if (playerData) {
// 请求AJAX接口获取播放列表
let playData = await request(playerData[1]);
// 解析playData...
}
}
return result;
},
```
## 调试技巧
### 使用log函数
```javascript
推荐: async function () {
let html = await request(this.homeUrl);
log('首页HTML长度:', html.length); // 输出到日志
log('HTML前500字符:', html.substring(0, 500));
// 继续解析...
return [];
},
```
### 分步调试
```javascript
一级: async function () {
try {
// 1. 获取页面
let html = await request(this.input);
// 2. 测试选择器
let testElements = pdfh(html, '.video-item');
log('找到元素数量:', testElements.length);
// 3. 提取第一个元素测试
if (testElements.length > 0) {
let firstTitle = pdfh(html, '.video-item:eq(0) .title&&Text');
log('第一个标题:', firstTitle);
}
// 4. 完整解析
return parseVideos(html);
} catch (error) {
log('解析错误:', error);
return [];
}
},
```
### 验证返回值格式
```javascript
// 调试时验证二级返回值
二级: async function () {
let result = await parseDetail(this.input);
// 验证必要字段
if (!result.vod_name) {
log('警告:缺少vod_name');
}
if (!result.vod_play_from || !result.vod_play_url) {
log('警告:缺少播放信息');
}
log('二级结果:', JSON.stringify(result, null, 2));
return result;
},
```
## 常见问题解决
### Q: 选择器匹配不到内容
A:
1. 检查是否有iframe或动态加载内容
2. 尝试更通用的选择器
3. 查看页面是否使用JavaScript渲染
4. 使用`log()`输出HTML片段检查结构
### Q: 图片获取不到
A:
1. 优先尝试`data-src`属性
2. 检查是否需要添加Referer
3. 使用`图片替换`属性处理CDN
4. 设置`图片来源`添加必要请求头
### Q: 播放列表为空
A:
1. 检查二级函数是否正确返回`vod_play_from`和`vod_play_url`
2. 验证播放地址格式是否正确($$分隔来源,#分隔剧集)
3. 检查是否需要`play_parse`或`lazy`处理
4. 使用`sniffer`功能辅助嗅探
### Q: 编码问题导致乱码
A:
1. 设置`编码: 'gbk'`或`编码: 'gb2312'`
2. 搜索使用独立编码:`搜索编码: 'gbk'`
3. 检查response的Content-Type头
FILE:references/templates.md
# drpy模板继承与修改
## 模板系统概述
drpy支持模板继承机制,允许新源继承现有模板的配置,只需覆盖或添加特定属性。这大大简化了源创建过程,特别是对于结构相似的网站。
## 可用模板
### 常用模板列表
| 模板名 | 描述 | 适用网站类型 |
|--------|------|-------------|
| mxpro | MX影视Pro模板 | 通用CMS影视站 |
| mxone | MX影视One模板 | 另一类CMS站 |
| 海螺 | 海螺影视模板 | 类似MX的CMS站 |
| 首图 | 首图模板 | 特殊布局网站 |
| 短视 | 短视频模板 | 短视频网站 |
| 采集 | 采集站模板 | 采集/聚合站 |
| 自动 | 自动适配模板 | CMS类站(自动检测) |
## 模板继承方法
### 方法一:Object.assign(传统方式)
```javascript
var rule = Object.assign(muban.mxpro, {
// 覆盖的属性
title: '鸭奈飞',
host: 'https://yanetflix.com',
// 添加的属性
url: '/index.php/vod/show/id/fyclass/page/fypage.html',
class_parse: `.navbar-items li:gt(1):lt(6);a&&Text;a&&href;.*/(.*?).html`,
// 可以覆盖模板的函数
推荐: async function () {
let {input} = this;
// 自定义推荐逻辑
return [];
},
});
```
### 方法二:模板属性(新方式)
```javascript
var rule = {
title: 'cokemv',
模板: 'mxpro', // 指定继承的模板
host: 'https://cokemv.me',
// 覆盖的属性
class_parse: `.navbar-items li:gt(1):lt(7);a&&Text;a&&href;/(\\d+).html`,
// 添加的属性
searchUrl: '/vodsearch/紧箍咒/page/fypage.html',
};
```
### 方法三:自动匹配模板
```javascript
var rule = {
模板: '自动', // 自动选择最匹配的模板
模板修改: $js.toString(() => {
// 修改模板的局部配置
Object.assign(muban.自动.二级, {
tab_text: 'div.small&&Text', // 修改二级选项卡文本选择器
list_url: 'a.data-src&&href', // 修改列表链接选择器
});
}),
// 源配置
title: '剧圈圈[自动]',
host: 'https://www.jqqzx.cc/',
// 必须提供的配置(用于自动匹配)
url: '/vodshow/id/fyclass/page/fypage.html',
searchUrl: '/vodsearch**/page/fypage.html',
class_parse: '.navbar-items li:gt(2):lt(8);a&&Text;a&&href;.*/(.*?)\.html',
cate_exclude: '今日更新|热榜',
};
```
**注意**:自动匹配只支持能从HOST获取分类的CMS模板站,采集站、短视频站等API模板无法自动匹配。
## 模板修改
### 修改模板函数
```javascript
// 修改特定模板的函数
模板修改: $js.toString(() => {
// 修改一级列表解析
Object.assign(muban.mxpro.一级, {
// 更改选择器
selector: '.video-item',
title: 'h3&&Text',
image: 'img&&data-src',
desc: '.info&&Text',
link: 'a&&href',
});
// 修改二级详情解析
Object.assign(muban.mxpro.二级, {
tab_text: '.play-source&&Text',
list_text: '.play-list-item&&Text',
list_url: 'a&&data-play',
});
// 添加自定义函数
muban.mxpro.自定义函数 = async function () {
// 自定义逻辑
return result;
};
}),
```
### 批量修改多个模板
```javascript
模板修改: $js.toString(() => {
// 修改多个模板的通用配置
const templates = ['mxpro', 'mxone', '海螺'];
templates.forEach(templateName => {
if (muban[templateName]) {
// 统一修改headers
muban[templateName].headers = {
'User-Agent': 'MOBILE_UA',
'Referer': HOST,
};
// 统一修改超时
muban[templateName].timeout = 8000;
}
});
}),
```
## 模板组合
### 继承多个模板
```javascript
// 先继承一个模板,再混入另一个模板的部分配置
var baseTemplate = Object.assign({}, muban.mxpro);
var combinedTemplate = Object.assign(baseTemplate, {
// mxpro的配置
...muban.mxpro,
// 混入首图模板的某些特性
推荐: muban.首图.推荐,
一级: muban.首图.一级,
// 自定义配置
title: '混合模板源',
host: 'https://example.com',
});
```
### 创建自定义模板
```javascript
// 定义自定义模板(可存储在单独的JS文件中)
var myCustomTemplate = {
// 基础配置
type: '影视',
searchable: 1,
quickSearch: 0,
filterable: 0,
headers: {
'User-Agent': 'MOBILE_UA',
},
timeout: 5000,
// 解析函数
推荐: async function () {
let {input} = this;
return [];
},
一级: async function () {
let {input} = this;
return [];
},
二级: async function () {
let {input} = this;
return {};
},
搜索: async function () {
let {input} = this;
return [];
},
};
// 在源中使用自定义模板
var rule = Object.assign(myCustomTemplate, {
title: '使用自定义模板',
host: 'https://example.com',
url: '/fyclass/fypage.html',
});
```
## 模板查看与调试
### 查看模板内容
```javascript
// 在浏览器控制台查看模板
console.log(muban.mxpro); // 查看mxpro模板完整结构
console.log(muban.mxpro.一级); // 查看一级函数
console.log(muban.mxpro.headers); // 查看headers配置
```
### 调试模板继承
```javascript
// 创建调试函数查看最终合并的规则
var rule = Object.assign(muban.mxpro, {
title: '调试源',
host: 'https://debug.com',
});
// 打印最终规则(开发时使用)
console.log('最终规则:', JSON.stringify(rule, null, 2));
```
## 最佳实践
### 1. 选择合适的模板
- **CMS影视站** → mxpro、mxone
- **短视频站** → 短视
- **采集聚合站** → 采集
- **不确定类型** → 自动(CMS站优先)
### 2. 最小化修改
```javascript
// 推荐:只修改必要的属性
var rule = {
模板: 'mxpro',
title: '源名称',
host: 'https://example.com',
// 只修改class_parse以适配网站分类结构
class_parse: '.menu li;a&&Text;a&&href;/(\\d+)/',
};
// 不推荐:过度修改
var rule = Object.assign(muban.mxpro, {
title: '源名称',
host: 'https://example.com',
// 修改大量函数(除非必要)
推荐: function() { /* 完全重写 */ },
一级: function() { /* 完全重写 */ },
二级: function() { /* 完全重写 */ },
搜索: function() { /* 完全重写 */ },
});
```
### 3. 保留模板默认值
除非有特殊需求,否则保留模板的默认配置:
- headers
- timeout
- play_parse
- double等布局参数
### 4. 验证模板兼容性
创建源后,测试:
- 分类获取是否正确
- 一级列表是否能正常显示
- 二级详情是否完整
- 搜索功能是否正常
- 播放是否可用
## 常见问题
### Q: 模板继承后某些功能不工作
A: 检查是否意外覆盖了模板的关键函数或属性。
### Q: 自动模板不匹配
A: 确保网站是标准CMS结构,并提供`url`、`searchUrl`、`class_parse`等必要配置。
### Q: 如何知道模板有哪些可配置项?
A: 使用`console.log(muban.模板名)`查看模板结构,或参考现有成功源的配置。
### Q: 可以继承多个模板吗?
A: 不能直接继承多个,但可以通过`Object.assign`组合多个模板的配置。
### Q: 模板修改不生效
A: 确保`模板修改`函数正确使用`$js.toString()`包装,并在函数内正确修改模板对象。
FILE:references/troubleshooting.md
# drpy源问题排查指南
## 诊断流程
### 快速诊断流程图
```
开始
↓
检查host和网络连接 → 失败 → 修复网络/域名
↓ 成功
检查首页访问 → 失败 → 检查headers/UA
↓ 成功
检查分类获取 → 失败 → 调试class_parse
↓ 成功
检查一级列表 → 失败 → 调试选择器
↓ 成功
检查二级详情 → 失败 → 查看页面结构
↓ 成功
检查播放功能 → 失败 → 检查lazy/sniffer
↓ 成功
源正常工作 ✓
```
## 常见问题分类
### 连接与网络问题
#### 1. 访问超时 (timeout)
**症状**:源加载失败,提示超时或网络错误
**可能原因**:
- `host`配置错误
- 网站服务器响应慢
msg- `timeout`设置太短
- 网络防火墙/代理限制
**解决方案**:
```javascript
// 增加超时时间
timeout: 10000, // 默认3000,建议5000-10000
// 检查host格式
host: 'https://www.example.com', // 必须有协议头
// 设置更宽容的headers
headers: {
'User-Agent': 'Mozilla/5.0 ...', // 使用完整UA
'Accept-Encoding': 'gzip, deflate',
},
```
#### 2. 403/404错误
**症状**:返回HTTP错误状态码
**可能原因**:
- 网站反爬虫
- 需要特定Referer
- 路径错误
**解决方案**:
```javascript
headers: {
'User-Agent': 'MOBILE_UA',
'Referer': 'https://www.example.com/', // 添加Referer
'Cookie': '关键cookie值', // 可能需要cookie
},
// 或者使用PC_UA
headers: {
'User-Agent': 'PC_UA',
},
```
### 解析与选择器问题
#### 3. 分类获取失败
**症状**:分类列表为空或显示"获取失败"
**可能原因**:
- `class_parse`选择器错误
- 网站分类结构变化
- 正则表达式错误
**调试步骤**:
```javascript
// 临时添加调试代码
预处理: `js:
let html = request(HOST + homeUrl);
log('首页HTML长度:', html.length);
log('分类区域HTML:', pdfh(html, '选择器'));
// 测试选择器
let test = pdfh(html, '.navbar li&&Text');
log('测试选择器结果:', test);
`,
```
**解决方案**:
```javascript
// 更新class_parse选择器
class_parse: '.new-nav li;a&&Text;a&&href;/(\\d+)/',
// 或者使用更通用的选择器
class_parse: 'li:has(a);a&&Text;a&&href;.*/(.*?)/',
```
#### 4. 一级列表为空
**症状**:分类下有分类但不能显示视频列表
**可能原因**:
- 一级选择器错误
- 页面为AJAX动态加载
- URL模板错误
**调试方法**:
```javascript
// 在函数中添加调试
一级: async function () {
log('一级URL:', this.input); // 查看实际请求的URL
let html = await request(this.input);
log('页面长度:', html.length);
log('前1000字符:', html.substring(0, 1000));
// 测试选择器
let test = pdfh(html, '.video-item&&Text');
log('选择器测试:', test);
return [];
},
```
**解决方案**:
```javascript
// 更新选择器
一级: '.new-video-list .item;.title&&Text;img&&data-src;.info&&Text;a&&href',
// 或者使用异步函数处理动态内容
一级: async function () {
let html = await request(this.input);
// 可能需要处理JavaScript渲染的内容
if (html.includes('window.__DATA__')) {
// 提取JSON数据
let jsonData = html.match(/window\.__DATA__ = (\{.*?\});/s)[1];
return parseJSONData(jsonData);
}
return parseNormalHTML(html);
},
```
#### 5. 二级详情错误
**症状**:无法进入详情页或详情页信息不完整
**可能原因**:
- 详情页链接错误
- 选择器不匹配
- 播放信息提取失败
**调试代码**:
```javascript
二级: async function () {
log('二级URL:', this.input);
let html = await request(this.input);
log('详情页长度:', html.length);
// 测试各种选择器
let title = pdfh(html, 'h1&&Text');
log('标题:', title);
let playTabs = pdfh(html, '.play-source&&Text');
log('播放选项卡:', playTabs);
let playList = pdfh(html, '.play-list&&html');
log('播放列表HTML:', playList);
return {
vod_name: title,
vod_play_from: playTabs || '默认',
vod_play_url: '#',
};
},
```
### 播放相关问题
#### 6. 无法播放视频
**症状**:点击播放按钮无效或提示"解析失败"
**可能原因**:
- 播放地址提取错误
- 需要免嗅解析
- 视频地址失效
**检查清单**:
1. **检查play_parse设置**:
```javascript
play_parse: true, // 必须为true
```
2. **检查lazy函数**:
```javascript
lazy: async function () {
let {input} = this;
log('lazy输入:', input);
// 如果input是m3u8等可直接播放的地址
if (/\.m3u8|\.mp4/.test(input)) {
return {
url: input,
parse: 0, // 0表示直接播放
};
}
// 需要进一步解析
let playUrl = await extractRealUrl(input);
return {
url: playUrl,
parse: 0,
};
},
```
3. **启用辅助嗅探**:
```javascript
sniffer: 1,
isVideo: "http((?!http).){26,}\\.(m3u8|mp4|flv|avi|mkv|wmv|mpg|mpeg|mov|ts|3gp|rm|rmvb|asf|m4a|mp3|wma)",
```
#### 7. 播放卡顿或缓冲
**可能原因**:
- 视频源服务器限速
- 线路拥挤
- 需要代理
**解决方案**:
```javascript
// 使用本地代理优化
proxy_rule: `js:
if(input.includes('.m3u8')) {
// 处理m3u8文件,例如去除广告片段
let content = request(input);
let cleaned = content.replace(/#EXTINF.*?\\n.*?ad.*?\\n/g, '');
input = [200, 'application/vnd.apple.mpegurl', cleaned];
}
`,
```
### 特定类型源问题
#### 8. 漫画源显示问题
**症状**:漫画图片无法加载或显示异常
**解决方案**:
```javascript
// 漫画源lazy必须返回pics://协议
lazy: async function () {
let images = await extractImageUrls(this.input);
return `pics://images.join(',')`;
},
// 图片可能需要特殊处理
图片替换: 'https://old-cdn.com/=>https://new-cdn.com/',
图片来源: '@Referer=https://comic.site.com/',
```
#### 9. 小说源格式错误
**症状**:小说内容显示乱码或无法阅读
**解决方案**:
```javascript
// 小说源lazy必须返回novel://协议
lazy: async function () {
let content = await extractNovelContent(this.input);
return `novel://"章节标题",
content: content,)}`;
},
// 确保编码正确
编码: 'utf-8',
```
### 性能与缓存问题
#### 10. 加载缓慢
**症状**:源响应慢,影响使用体验
**优化建议**:
```javascript
// 1. 适当减少请求数据
limit: 20, // 减少首页推荐数量
// 2. 使用缓存
预处理: `js:
// 缓存分类数据
if(!global._categoryCache) {
global._categoryCache = {};
}
let cacheKey = HOST + '分类';
if(global._categoryCache[cacheKey]) {
return global._categoryCache[cacheKey];
}
// ...获取分类
global._categoryCache[cacheKey] = result;
return result;
`,
// 3. 优化选择器(避免过于复杂)
一级: '.video-item;h3&&Text;img&&src', // 简化选择器
```
#### 11. 修改不生效
**症状**:修改JS文件后配置未更新
**原因**:drpy有配置缓存机制
**解决方案**:
1. **猫影视/TVBox**:进入设置 → 换源 → 重新选择源
2. **海阔视界**:长按源 → 更新配置
3. **重启播放器**:完全退出后重新启动
4. **清空缓存**:设置中清空播放器缓存
### 高级调试技巧
#### 12. 日志分析
启用详细日志:
```javascript
预处理: `js:
// 记录所有请求
let originalRequest = request;
request = function(url, options) {
log('请求URL:', url);
log('请求选项:', options);
try {
let result = originalRequest(url, options);
log('响应长度:', result.length);
return result;
} catch (error) {
log('请求错误:', error);
throw error;
}
};
`,
```
#### 13. 模拟请求测试
使用外部工具测试选择器:
1. **浏览器开发者工具**:
- 打开目标网站
- 在Console测试$('选择器')
- 验证选择器是否正确
2. **在线测试工具**:
- 使用正则表达式测试工具
- 验证class_parse中的正则
3. **编写测试脚本**:
```javascript
// 独立的测试脚本
let testHtml = `获取的HTML内容`;
let title = pdfh(testHtml, '.video-title&&Text');
console.log('提取的标题:', title);
```
## 紧急修复模板
### 分类获取紧急修复
```javascript
// 当class_parse失效时,临时使用静态分类
class_name: '电影&电视剧&动漫&综艺',
class_url: '1&2&3&4',
// 并尝试获取动态分类(备用)
class_parse: `js:
let html = request(HOST + homeUrl);
// 尝试多种选择器
let selectors = [
'.navbar li a',
'.menu li a',
'ul.nav li a',
'#nav li a'
];
for(let selector of selectors) {
let result = pdfh(html, selector + '&&Text');
if(result && result.length > 0) {
log('找到选择器:', selector);
// 返回处理后的分类
return result;
}
}
log('警告:未找到分类选择器');
return ''; // 返回空,使用静态分类
`,
```
## 实战案例:选择器不匹配修复
### 问题描述
参考代码使用 `.remarks&&Text` 获取状态,但实际HTML中class为 `.hl-pic-text span`。
### 修复过程
**第一步:确认问题**
```python
import requests
from bs4 import BeautifulSoup
html = requests.get('https://www.pitv.cc/').text
soup = BeautifulSoup(html, 'html.parser')
# 检查.remarks是否存在
print('remarks:', bool(soup.find(class_='remarks'))) # False
# 查找实际的class
for elem in soup.find_all(class_=True):
if elem.get('class') and 'pic' in str(elem.get('class')):
print(elem.name, elem.get('class'))
# 输出: div ['hl-pic-text']
```
**第二步:修正选择器**
```javascript
// 错误
推荐: '.hl-vod-list;li;a&&title;a&&data-original;.remarks&&Text;a&&href',
// 正确
推荐: '.hl-vod-list;li;a&&title;a&&data-original;.hl-pic-text span&&Text;a&&href',
```
**第三步:验证修复**
- 重新加载源
- 检查首页是否显示状态
- 检查分类列表是否正常
### 经验总结
1. **不要盲信参考代码** - 每个网站的HTML结构不同
2. **必须实际验证** - 用Python获取HTML确认选择器存在
3. **注意class变化** - 网站改版可能导致class名变化
4. **使用模糊匹配** - 如 `img&&data-original||img&&src` 提高兼容性
### 播放紧急修复
```javascript
// 当原有播放解析失效时
lazy: async function () {
let {input} = this;
// 尝试多种方式获取播放地址
let playUrl = input;
// 方法1:直接使用输入
if (/\.(m3u8|mp4)/.test(input)) {
playUrl = input;
}
// 方法2:从页面重新提取
else {
let html = await request(input);
let patterns = [
/"url":"([^"]+\.m3u8[^"]*)"/,
/src="([^"]+\.mp4[^"]*)"/,
/file:"([^"]+)"/,
];
for(let pattern of patterns) {
let match = html.match(pattern);
if(match && match[1]) {
playUrl = match[1];
break;
}
}
}
return {
url: playUrl,
parse: 0,
};
},
```
## 预防性维护建议
### 定期检查
1. **每月检查**:主要源的功能是否正常
2. **选择器备份**:记录多个备选选择器
3. **模板更新**:关注drpy框架更新,及时适配新模板
### 代码管理
1. **版本控制**:使用Git管理源文件
2. **注释说明**:关键代码添加详细注释
3. **测试用例**:为复杂源编写测试代码
### 用户反馈
1. **错误收集**:记录用户报告的播放问题
2. **解决方案库**:建立常见问题的解决方案
3. **更新通知**:重大修复及时通知用户
## 获取帮助
### 社区资源
1. **官方文档**:查看最新drpy文档
2. **GitHub仓库**:提交issue和查看解决方案
3. **用户论坛**:交流经验和技术
### 调试工具推荐
1. **浏览器开发者工具**:测试选择器
2. **正则表达式测试工具**:验证正则
3. **网络抓包工具**:分析请求响应
记住:drpy源的调试需要耐心和系统的方法。从连接问题开始,逐步排查到解析问题,最后解决播放问题。
FILE:scripts/analyze_site.py
#!/usr/bin/env python3
"""
drpy源网站结构分析工具
用于快速分析影视网站的HTML结构,生成drpy源配置建议
使用方法:
python analyze_site.py https://www.example.com
输出信息:
- 网站基本信息
- 视频列表容器class
- 关键选择器检测结果
- drpy源配置建议
"""
import requests
import re
import sys
from bs4 import BeautifulSoup
def analyze_site(url):
"""分析网站结构"""
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36',
'Referer': url
}
print(f'\n{"="*60}')
print(f'正在分析: {url}')
print(f'{"="*60}\n')
try:
resp = requests.get(url, headers=headers, timeout=10)
resp.encoding = 'utf-8'
html = resp.text
soup = BeautifulSoup(html, 'html.parser')
# 基本信息
print('【基本信息】')
print(f' 状态码: {resp.status_code}')
print(f' HTML长度: {len(html)} 字符')
# 查找标题
title = soup.find('title')
if title:
print(f' 网站标题: {title.get_text(strip=True)}')
# 查找视频列表相关class
print('\n【视频列表分析】')
# 常见的视频列表class
common_list_classes = [
'vod-list', 'video-list', 'movie-list', 'film-list',
'hl-vod-list', 'module-list', 'list-item',
'stui-vodlist', 'myui-vodlist', 'fed-vodlist'
]
found_lists = []
for cls in common_list_classes:
if soup.find(class_=re.compile(cls)):
found_lists.append(cls)
if found_lists:
print(f' 找到的视频列表class: {", ".join(found_lists)}')
else:
print(' 未找到常见视频列表class,建议手动查找')
# 查找视频链接
vod_links = soup.find_all('a', href=re.compile(r'/vod/|/video/|/movie/|/detail/'))
print(f' 视频链接数量: {len(vod_links)}')
if vod_links:
# 分析第一个视频项的结构
first_link = vod_links[0]
print(f'\n【视频项结构分析】')
print(f' 示例链接: {first_link.get("href")}')
print(f' 标题: {first_link.get_text(strip=True)[:50]}')
# 向上查找父元素
parent = first_link.find_parent()
for i in range(3):
if parent:
cls = parent.get('class')
print(f' 父元素{i+1}: {parent.name} class={cls}')
# 查找图片
img = parent.find('img')
if img:
data_src = img.get('data-original') or img.get('data-src')
src = img.get('src')
print(f' 图片data-original: {data_src[:80] if data_src else "无"}')
print(f' 图片src: {src[:80] if src else "无"}')
parent = parent.parent
# 检查关键选择器
print('\n【关键选择器检测】')
selectors = {
'列表容器': ['.hl-vod-list', '.vod-list', '.video-list', '.module-list'],
'列表项': ['.hl-list-item', '.list-item', '.vod-item', '.video-item'],
'标题': ['a&&title', 'h3&&Text', '.title&&Text'],
'图片': ['img&&data-original', 'img&&data-src', 'img&&src'],
'状态/备注': ['.remarks&&Text', '.pic-text&&Text', '.item-status&&Text'],
}
for name, patterns in selectors.items():
found = False
for pattern in patterns:
# 简化的检测
cls = pattern.replace('&&Text', '').replace('a&&', '').replace('img&&', '').replace('h3&&', '').replace('.', '')
if soup.find(class_=re.compile(cls)):
print(f' ✓ {name}: {pattern} 可能存在')
found = True
break
if not found:
print(f' ✗ {name}: 未找到常见模式')
# 检查分类
print('\n【分类分析】')
type_links = soup.find_all('a', href=re.compile(r'/type/|/class/|/category/'))
if type_links:
print(f' 找到 {len(type_links)} 个分类链接')
for link in type_links[:5]:
print(f' - {link.get_text(strip=True)}: {link.get("href")}')
# 检查搜索
print('\n【搜索功能分析】')
search_forms = soup.find_all('form', action=re.compile('search'))
if search_forms:
for form in search_forms:
action = form.get('action', '')
print(f' 搜索表单action: {action}')
# 测试搜索是否需要验证码
test_search = requests.get(url + action, headers=headers, timeout=5)
if '验证码' in test_search.text or 'verify' in test_search.text:
print(' ⚠ 搜索需要验证码')
else:
print(' ✓ 搜索可能可用')
else:
print(' 未找到搜索表单')
# 生成建议配置
print('\n【drpy源配置建议】')
print('-' * 60)
# 提取域名
from urllib.parse import urlparse
parsed = urlparse(url)
host = f'{parsed.scheme}://{parsed.netloc}'
print(f'''var rule = {{
title: '{title.get_text(strip=True) if title else "影视源"}',
host: '{host}',
url: '/type/fyclass/',
// 根据分析结果调整选择器
class_name: '分类1&分类2&分类3',
class_url: '1&2&3',
// 推荐选择器(需要根据实际HTML调整)
推荐: '.hl-vod-list;li;a&&title;a&&data-original;;a&&href',
// 一级选择器
一级: '.hl-vod-list&&.hl-list-item;a&&title;a&&data-original;;a&&href',
// 二级选择器(需要查看详情页确认)
二级: '*',
// 搜索(根据分析结果决定是否启用)
searchable: 0,
lazy: ''
}};''')
print('-' * 60)
print('\n【下一步建议】')
print('1. 访问网站首页,查看视频列表的HTML结构')
print('2. 确认class名称,特别是列表容器和列表项')
print('3. 检查图片是在data-original还是src属性中')
print('4. 访问详情页,确认播放列表的选择器')
print('5. 测试搜索功能,确认是否需要验证码')
except Exception as e:
print(f'分析出错: {e}')
import traceback
traceback.print_exc()
if __name__ == '__main__':
if len(sys.argv) < 2:
print('用法: python analyze_site.py <网站URL>')
print('示例: python analyze_site.py https://www.pitv.cc/')
sys.exit(1)
url = sys.argv[1]
if not url.startswith('http'):
url = 'https://' + url
analyze_site(url)
FILE:scripts/minify_drpy.js
#!/usr/bin/env node
/**
* drpy源压缩脚本
* 使用uglify-js压缩drpy源代码
* 用法:node minify_drpy.js input.js [output.js]
*/
const fs = require('fs');
const path = require('path');
const { execSync } = require('child_process');
function main() {
const args = process.argv.slice(2);
if (args.length < 1) {
console.log('用法: node minify_drpy.js <输入文件> [输出文件]');
console.log('示例:');
console.log(' node minify_drpy.js source.js');
console.log(' node minify_drpy.js source.js source.min.js');
process.exit(1);
}
const inputFile = args[0];
const outputFile = args[1] || getMinifiedName(inputFile);
if (!fs.existsSync(inputFile)) {
console.error(`错误: 文件不存在 inputFile`);
process.exit(1);
}
console.log(`正在压缩: inputFile`);
try {
// 检查是否安装了uglify-js
try {
execSync('uglifyjs --version', { stdio: 'pipe' });
} catch (e) {
console.error('错误: 请先安装 uglify-js');
console.error('安装命令: npm install uglify-js -g');
process.exit(1);
}
// 使用uglifyjs压缩
const command = `uglifyjs "inputFile" -o "outputFile" --compress --mangle`;
execSync(command, { stdio: 'inherit' });
const inputSize = fs.statSync(inputFile).size;
const outputSize = fs.statSync(outputFile).size;
const ratio = ((1 - outputSize / inputSize) * 100).toFixed(2);
console.log(`\n压缩完成:`);
console.log(` 输入文件: inputFile (formatBytes(inputSize))`);
console.log(` 输出文件: outputFile (formatBytes(outputSize))`);
console.log(` 压缩率: ratio%`);
} catch (error) {
console.error('压缩失败:', error.message);
// 如果uglify失败,尝试简单的压缩
console.log('尝试简单压缩...');
simpleMinify(inputFile, outputFile);
}
}
function getMinifiedName(filename) {
const ext = path.extname(filename);
const name = path.basename(filename, ext);
const dir = path.dirname(filename);
return path.join(dir, `name.minext`);
}
function formatBytes(bytes) {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}
function simpleMinify(inputFile, outputFile) {
try {
let content = fs.readFileSync(inputFile, 'utf8');
// 简单的压缩步骤
let minified = content
// 移除注释
.replace(/\/\/.*$/gm, '')
.replace(/\/\*[\s\S]*?\*\//g, '')
// 移除多余的空格和换行
.replace(/\s+/g, ' ')
.replace(/\s*([{}();,+=&|])\s*/g, '$1')
.trim();
fs.writeFileSync(outputFile, minified, 'utf8');
const inputSize = content.length;
const outputSize = minified.length;
const ratio = ((1 - outputSize / inputSize) * 100).toFixed(2);
console.log(`简单压缩完成:`);
console.log(` 输入大小: inputSize 字符`);
console.log(` 输出大小: outputSize 字符`);
console.log(` 压缩率: ratio%`);
} catch (error) {
console.error('简单压缩也失败:', error.message);
process.exit(1);
}
}
if (require.main === module) {
main();
}
module.exports = { minifyDrpy: main };
FILE:scripts/validate_drpy.js
#!/usr/bin/env node
/**
* drpy源验证脚本
* 验证drpy源的基本格式和必需属性
*/
const fs = require('fs');
const path = require('path');
function main() {
const args = process.argv.slice(2);
if (args.length < 1) {
console.log('用法: node validate_drpy.js <drpy源文件>');
console.log('示例:');
console.log(' node validate_drpy.js ./my-source.js');
process.exit(1);
}
const inputFile = args[0];
if (!fs.existsSync(inputFile)) {
console.error(`错误: 文件不存在 inputFile`);
process.exit(1);
}
console.log(`验证文件: inputFile`);
try {
const content = fs.readFileSync(inputFile, 'utf8');
const result = validateDrpySource(content);
printValidationResult(result);
if (!result.isValid) {
process.exit(1);
}
} catch (error) {
console.error('验证失败:', error.message);
process.exit(1);
}
}
function validateDrpySource(content) {
const result = {
isValid: true,
warnings: [],
errors: [],
properties: {},
suggestions: []
};
// 检查基本结构
if (!content.includes('var rule =')) {
result.errors.push('缺少 "var rule =" 定义');
result.isValid = false;
}
// 提取属性(简单正则匹配)
const properties = extractProperties(content);
result.properties = properties;
// 检查必需属性
const requiredProps = ['title', 'host'];
for (const prop of requiredProps) {
if (!properties[prop]) {
result.errors.push(`缺少必需属性: prop`);
result.isValid = false;
}
}
// 检查重要属性
const importantProps = ['类型', 'url', 'searchUrl', 'class_name', 'class_url'];
for (const prop of importantProps) {
if (!properties[prop]) {
result.warnings.push(`建议添加属性: prop`);
}
}
// 检查函数定义
const requiredFunctions = ['推荐', '一级', '二级', '搜索'];
for (const func of requiredFunctions) {
if (!content.includes(`func:`)) {
result.warnings.push(`建议定义函数: func`);
}
}
// 检查常见问题
if (content.includes('fyclass') && !content.includes('class_name')) {
result.warnings.push('使用了fyclass但未定义class_name和class_url');
}
if (content.includes('**') && !content.includes('searchUrl')) {
result.warnings.push('使用了搜索占位符**但未定义searchUrl');
}
// 检查模板继承
if (content.includes('Object.assign')) {
result.properties.templateInheritance = 'Object.assign方式';
} else if (content.includes('模板:')) {
result.properties.templateInheritance = '模板属性方式';
}
// 分析可能的问题
analyzePotentialIssues(content, result);
return result;
}
function extractProperties(content) {
const props = {};
const lines = content.split('\n');
// 简单提取属性(基于冒号分隔)
for (const line of lines) {
const trimmed = line.trim();
// 跳过空行和注释
if (!trimmed || trimmed.startsWith('//')) {
continue;
}
// 匹配属性: 值
const propMatch = trimmed.match(/^([\w\u4e00-\u9fa5]+)\s*:/);
if (propMatch) {
const propName = propMatch[1];
// 提取值(简单版本)
const valueMatch = trimmed.match(/:\s*(.+)$/);
if (valueMatch) {
let value = valueMatch[1].trim();
// 清理尾随逗号
if (value.endsWith(',')) {
value = value.slice(0, -1);
}
// 清理字符串引号
if (value.startsWith("'") && value.endsWith("'") ||
value.startsWith('"') && value.endsWith('"')) {
value = value.slice(1, -1);
}
props[propName] = value;
}
}
}
return props;
}
function analyzePotentialIssues(content, result) {
// 检查URL模板
if (result.properties.url) {
const url = result.properties.url;
if (!url.includes('fyclass') && !url.includes('fypage')) {
result.warnings.push('url模板可能缺少fyclass或fypage占位符');
}
}
if (result.properties.searchUrl) {
const searchUrl = result.properties.searchUrl;
if (!searchUrl.includes('**')) {
result.warnings.push('searchUrl模板可能缺少**搜索词占位符');
}
}
// 检查编码问题
if (content.includes('gbk') || content.includes('GBK')) {
if (!result.properties.编码 && !result.properties.搜索编码) {
result.suggestions.push('检测到可能使用GBK编码的网站,建议设置编码或搜索编码属性');
}
}
// 检查可能的反爬措施需要
if (content.includes('403') || content.includes('反爬')) {
result.suggestions.push('网站可能有反爬措施,建议设置合适的headers和Referer');
}
// 检查play_parse设置
if (content.includes('play_parse')) {
if (content.includes('play_parse: false') || content.includes('play_parse:0')) {
result.warnings.push('play_parse设置为false可能影响播放解析');
}
}
}
function printValidationResult(result) {
console.log('\n=== drpy源验证报告 ===\n');
// 基本信息
console.log('基本状态:');
console.log(` ✓ 格式'无效'`);
if (result.properties.templateInheritance) {
console.log(` ✓ 模板继承: result.properties.templateInheritance`);
}
console.log('\n属性检查:');
Object.keys(result.properties).forEach(prop => {
if (!['templateInheritance'].includes(prop)) {
const value = result.properties[prop];
console.log(` prop: value`);
}
});
// 错误
if (result.errors.length > 0) {
console.log('\n❌ 错误:');
result.errors.forEach(error => {
console.log(` - error`);
});
}
// 警告
if (result.warnings.length > 0) {
console.log('\n⚠️ 警告:');
result.warnings.forEach(warning => {
console.log(` - warning`);
});
}
// 建议
if (result.suggestions.length > 0) {
console.log('\n💡 建议:');
result.suggestions.forEach(suggestion => {
console.log(` - suggestion`);
});
}
// 总结
console.log('\n=== 验证总结 ===');
if (result.isValid) {
console.log('✅ 源文件基本格式正确');
if (result.warnings.length === 0 && result.errors.length === 0) {
console.log('🎉 未发现明显问题,可以尝试加载测试');
} else {
console.log('📝 请检查上面的警告和建议');
}
} else {
console.log('❌ 源文件存在问题,需要修复');
}
// 下一步建议
console.log('\n下一步:');
if (!result.isValid) {
console.log('1. 修复上述错误');
}
console.log('2. 在播放器中测试源功能');
console.log('3. 检查分类、列表、详情、搜索功能是否正常');
console.log('4. 测试播放功能');
}
if (require.main === module) {
main();
}
module.exports = { validateDrpySource, validateDrpyFile: main };