書源規則對接指南
/
已過濾顯示符合的內容
對接文件

書源 JSON 對接指南

這份文件提供書源結構、必要欄位、功能區塊與常見配置方式。
可先快速開始建立骨架,再依 request / response 欄位說明完成來源支援的功能。

01

結構總覽

最外層只有 infodata。先確認骨架正確,再分別補上請求規則與解析規則。

02

快速開始#

新增書源時可依下列順序處理。每一步都對應後續章節,可先完成最小可用版本,再補齊特殊情境。

從範例複製骨架

下載範例 找一個結構接近的書源,複製到新檔案。或直接使用 最小模板

設定 info 基本資料

修改 typenameindex
type 固定 99
index 建議使用 1000 以上的數字避免與既有書源衝突

配 request 抓頁面

apiHost / apiPath / httpMethod 遇到 GBK 編碼時,調整 charEncodingRawValue
需要繞 Cloudflare 改 requestMethodRawValue: 1

配 response 解析資料

用 DevTools 找 CSS selector,先寫 listElement,再寫各 *Element
actionValue 決定取什麼。
若解析結果包含站台 host,可用 hostsToReplace 移除指定 host 字串,讓後續 request 使用相同的 path 格式。

實機驗證

將書源 JSON 匯入 App,先測基本流程:首頁、詳細頁、章節目錄、章節正文。
若來源有分類列表或搜尋時,再分別測試。

03

流程一覽#

每個書源建議先驗證「首頁 → 詳細 → 章節 → 正文」主流程。
分類與搜尋屬於額外功能,若網站不提供時,可省略對應規則。
同一段要同時檢查 request 與 response,因為 URL 組法會直接影響解析結果的路徑格式。

建立 info名稱、索引、是否允許搜尋與下載
配 requesthost、path、method、編碼、WebView、header
配 responseCSS selector、actionValue、hostsToReplace 與 pathRegex 路徑正規化一併完成
跑 validator 實測先測首頁、詳細、章節、正文;分類、搜尋有提供時再測
04

info:書源資訊#

info 書源基本資訊,例如書源名稱、是否允許搜尋、是否允許下載。index 建議使用 1000 以上避免與既有書源衝突。

欄位 型別 必填 用途 對接注意
type Int 書源類型 自訂書源固定 99
name String 顯示名稱 書源標題
alias String 顯示別名 為空值時,系統預設使用name name
index Int 唯一索引 建議 1000+,新增取現有最大 +1
sortIndex Int 排序索引 數值越小排序越前面
version Int 規則版本 初始 1,更新規則時遞增
enabled Bool 是否啟用 固定 true
allowSearch Bool 是否允許搜尋 不支援時設 false,並可省略 search 規則
allowDownload Bool 是否允許下載 章節內容被拆成分頁,且從目錄 URL 無法得知內容頁數時設 false
imageURL String 列表圖示 站台 logo / favicon
backgroundColor [Double] 列表背景 RGBA 陣列,如 [230,240,235,1];A 範圍 0 ~ 1
05

request:請求規則#

每個頁面請求都有兩塊:settings 管 URL / method / header,config 管編碼與載入方式。先確認要用 URLSession 或 WebView,再組 URL。

config — 載入與編碼

charEncodingRawValue: 0 GBK 1 BIG5 2 UTF-8
requestMethodRawValue: 0 URLSession 1 WebView

charEncodingRawValue 根據來源編碼進行調整
requestMethodRawValue根據來源進行調整,1 可處理 Cloudflare,若需跨頁保留 cookie,再設定 websiteDataStoreRawValue: 1

settings — URL 組裝

apiHost 不含結尾斜線;apiPath/ 開頭。組合順序:apiHost + apiPathPrefix + apiPath + 分頁前綴/索引 + apiPathSuffix + extraApiPathSuffix

分頁常用 apiPageIndexPrefixapiPathSuffixapiPathReplacements;搜尋看 apiQueryTypeRawValue 決定 query 還是 path 模式。

settings 欄位 — 按用途分群

先依用途找到欄位群組,再對照範例填寫實際值。

必填基本每個 request 都要有
  • apiHostString請求 host,不含結尾 /
  • apiPathString請求路徑,以 / 開頭
  • httpMethodString僅支援 GET / POST
URL 組裝分頁 / 第一頁特殊路徑
  • apiPathPrefixString附加在 apiPath 之前
  • apiPathSuffixString.html
  • apiPageIndexPrefixString分頁索引前綴,分類常用
  • extraApiPathSuffixString關鍵字寫入路徑後再加固定段
  • apiPathReplacementsMap第一頁與後續頁 URL 不同時用
  • finalURLReplacementsMap組合後對最終 URL 做整段替換
查詢參數搜尋 / 分類 query
  • apiQueryKeyString搜尋關鍵字鍵,系統會塞入關鍵字
  • apiQueryParamsMap其餘固定 query 參數
  • apiQueryTypeRawValueInt0 無 / 1 參數 / 2 路徑
  • apiQueryPageIndexKeyString分頁帶 query 時用
  • apiQueryLangRawValueInt0 繁體 / 1 簡體
  • apiQueryValueFromPathMap從 path 抽出 query 值,分類常用
分頁控制分類列表
  • shouldIgnorePageIndexBool無分頁的分類設 true
  • ignorePageIndexByKeywords[String]含特定關鍵字才忽略分頁
請求控制header / 延遲 / 編碼
  • httpHeadersMap值為 request_url 代入完整連結
  • timeoutDouble逾時秒數,預設 45
  • delaySecondsDouble固定延遲,預設 0
  • randomDelaySeconds[Double]隨機區間 [min, max]
  • needsURLEncodingBoolGET 已自動編碼,避免雙重
  • needsCustomUserAgentBool使用客製 UA
動態參數JS 加密 / 簽名
  • jsScripts[JSScript]注入 JS 算 query。系統自動帶入 keywords

config 欄位

編碼 / 載入
  • charEncodingRawValueIntPayload 編碼:0 GBK / 1 BIG5 / 2 UTF-8(預設)
  • requestMethodRawValueInt0 URLSession / 1 WebView
  • websiteDataStoreRawValueInt0 不持久化 / 1 持久化;cookie 跨頁時用 1
WebView 注入
  • bodySelectorString等到此元素出現再解析
  • mainFrameOnlyBooltrue 僅主頁面 / false 含 iframe
  • injectionTimeInt0 頁面開始前 / 1 DOM 建完
!
搜尋模式判斷 apiQueryTypeRawValue: 1 代表 GET query,2 代表把關鍵字寫入 path。Path 模式通常不設needsURLEncoding: true,避免雙重編碼。
06

response:解析規則#

將 HTML 或 JSON 轉成 App 可用資料。通常先用 listElement 找出多筆項目,如首頁、章節、搜尋等功能,詳情,再用各種 *Element 從每個項目內取值。

config

Response 編碼與 Cloudflare 驗證設定。

charEncodingRawValue challengeKeywords challengeVerifiedKeywords shouldReload authStorageTypeRawValue waitForCookie

hostsToReplace

解析 path 時,系統會把結果中符合清單的 host 字串替換成空字串。
用途是讓絕對連結與相對連結輸出成一致的 path 格式。

只會處理列出的字串。
同站若同時有 https://http://www 或其他鏡像網域,需分別列入。

imageHost

封面解析到相對路徑時補圖片 host。多個圖片網域時須確認真實 CDN。

07

ParseElement 欄位#

所有 *Element 都是 ParseElement。必要欄位通常是 cssSelectoractionValue,其他欄位用於屬性提取、文字清理、前後綴補齊、JSON 包裝或特殊字串解析。

必填核心每個 Element 必填
  • cssSelectorStringCSS 選擇器;定位元素
  • actionValueInt解析類型,見 9 種對照
選擇與屬性取屬性 / 二級結構
  • attributeKeyString屬性名稱(href / src / data-src
  • selectCssSelectorString先定位指定元素,多筆中取一筆
  • selectIndexIntselectCssSelector 多結果取索引
  • cssSelector2String同頁兩種結構備援
  • selectCssSelector2String備援的指定元素
  • selectIndex2Int備援的索引
文字清理取得後處理
  • prefixString取得後加前綴(拼 URL 用)
  • suffixString取得後加後綴
  • separatorString正文段落串接符號
  • replacementsMapkey=結果文字 / value=要被替換的字
  • blankReplacementsMap替換為空白;key "0" 字面 / "1" 正則
  • rawContentReplacementsMap解析前對原始內容替換
  • removeElements[String]解析前移除子元素(廣告 / script)
陣列收集actionValue: 2
  • collectionSeparatorStringarray 合併符號
  • collectionActionValueInt陣列內元素解析類型
  • collectionValueIndexes[Int]取陣列指定索引
HTML 控制正文清理常用
  • invalidTags[String]含此標籤則略過(actionValue: 22)
  • replaceTags[String]標籤替換為換行(p / br 互轉)
  • shouldNormaliseBool解析前補齊未閉合標籤
  • shouldUnescapeUnicodeBool\uXXXX → 文字
特殊解析JSON / 非標準字串
  • parseableItems[Item]裁切非標準字串(actionValue: 20)
  • isJSONFormatBool原始內容是 JSON,包成 <ul><li>
  • jsonKeyString只取 JSON 某 key
  • orderedKeys[String]JSON 鍵輸出順序,用 nth-of-type 對應

isJSONFormat — JSON 回應處理

當某環節(常見於 chaptersearch 的 AJAX 介面)回傳 JSON 而非 HTML 時,於該環節的 listElementisJSONFormat: true。框架會先把 JSON 遞迴展開成 HTML 列表 —— 物件/陣列的每個值包成 <li>,最外層套 <div id="json-content"> —— 之後即可照常用 cssSelector 解析。

  • jsonKey:只包裝指定鍵的內容。如 API 回 {"status":0,"data":[…]},設 "data" 可略過外層 status
  • orderedKeys:指定輸出欄位與順序;子元素再依此順序用 nth-of-type(n) 取值,未列出的鍵會被忽略。
  • cssSelector 一律從 #json-content 起算,列表通常為 #json-content > ul > li
  • 建議搭配 shouldUnescapeUnicode(還原 \uXXXX)與 shouldNormalise(補齊標籤)。
  • JSON 只給 ID 而非完整路徑時,用 prefix / suffix 拼回合法路徑。

JSON → HTML 轉換

// API 原始回應
{ "data": [ { "ordernum": "1", "title": "第一章" } ] }

// 框架自動轉換後(供 cssSelector 解析)
<div id="json-content">
  <ul><li>
    <ul><li>1</li><li>第一章</li></ul>
  </li></ul>
</div>

response.chapter 配置

{
  "chapter": {
    "listElement": {
      "isJSONFormat": true,
      "jsonKey": "data",
      "orderedKeys": ["ordernum", "title"],
      "cssSelector": "#json-content > ul > li",
      "shouldUnescapeUnicode": true,
      "shouldNormalise": true
    },
    "pathElement": { "cssSelector": "ul li:nth-of-type(1)", "actionValue": 0,
                     "prefix": "p", "suffix": ".html" },
    "nameElement": { "cssSelector": "ul li:nth-of-type(2)", "actionValue": 0 }
  }
}
08

actionValue 對照#

actionValue 決定從元素內取得哪一種內容。常用值是 0 取文字、10 取屬性、2 多筆合併、22 解析段落正文。

名稱 用途 常見搭配 典型情境
0 text 取元素文字 replacements, blankReplacements 書名、作者、章節名
2 array 取多元素合併 collectionSeparator, collectionValueIndexes, collectionActionValue 多個分類標籤、多個作者
5 lastText 取最後一個文字 同組連結中的「下一章」
6 lastAttr 取最後一個屬性 attributeKey 同組連結中的「下一章」href
9 firstText 取第一個文字 同組連結中的「上一章」
10 firstAttr 取第一個屬性 attributeKey 連結 href、圖片 src、meta content
19 brTag 用 br 切正文 separator, removeElements 傳統小說頁 <br> 換行
20 component 裁切非標準字串 parseableItems 資料藏在混合字串中
22 pTag 用 p 標籤切正文 separator, invalidTags, replaceTags 現代小說頁 <p> 段落
09

功能區塊對照#

這裡列出書源常見的 request / response 區塊。
Detail、Chapter、Content 通常是閱讀主流程。
Home、Category、Search 則依來源是否提供對應功能決定是否配置;若 inPage: true,代表資料在同一頁,可省略額外 request。

Home — 首頁推薦與分類

首頁通常解析推薦小說與分類標籤。request.home.inPage: true 代表分類也在首頁;false 則需補 home.category 請求。response.home 同時包含 main(推薦)與 category(分類)。

main category? template (0 grid / 1 image / 2 num) pathRegex excludePaths

request.home

{
  "home": {
    "main": {
      "settings": {
        "apiHost": "https://example.com",
        "apiPath": "/",
        "httpMethod": "GET"
      },
      "config": { "charEncodingRawValue": 2 }
    },
    "inPage": true
  }
}

response.home

{
  "home": {
    "template": 0,
    "main": {
      "listElement": { "cssSelector": ".book-list li" },
      "nameElement":  { "cssSelector": "a", "actionValue": 0 },
      "pathElement":  { "cssSelector": "a", "actionValue": 10, "attributeKey": "href" },
      "imageElement": { "cssSelector": "img", "actionValue": 10, "attributeKey": "src" },
      "pathRegex": "^/book/\\d+/?$"
    },
    "category": {
      "listElement": { "cssSelector": "nav.cat a" },
      "nameElement": { "cssSelector": "self", "actionValue": 0 },
      "pathElement": { "cssSelector": "self", "actionValue": 10, "attributeKey": "href" }
    }
  }
}

Detail — 書籍資訊

解析封面、作者、簡介、分類、更新時間與最新章節名。若章節目錄不在詳細頁,需提供 chapterPathElement + chapterPathRegex;多數站章節在同頁,省略即可。

imageElement authorElement summaryElement categoryElement updatedTimeElement chapterPathElement (跨頁時)

request.detail

{
  "detail": {
    "main": {
      "settings": { "apiHost": "https://example.com", "apiPath": "/", "httpMethod": "GET" },
      "config":   { "charEncodingRawValue": 2 }
    },
    "inPage": false
  }
}

response.detail

{
  "detail": {
    "imageElement":  { "cssSelector": ".cover img", "actionValue": 10, "attributeKey": "src" },
    "authorElement": { "cssSelector": ".author",    "actionValue": 0 },
    "summaryElement":{ "cssSelector": ".intro",     "actionValue": 0 },
    "categoryElement":{ "cssSelector":".tag",       "actionValue": 2,  "collectionSeparator": ",",
                         "collectionActionValue": 0 },
    "updatedTimeElement":{ "cssSelector": ".updated","actionValue": 0 },
    "updatedTimeFormat": "yyyy-MM-dd"
  }
}

Chapter — 章節目錄

核心欄位是 listElementpathElementnameElement
完整目錄用 isPartial: false
分頁目錄則需提供分頁請求與頁數解析方式,例如 pageCountpageCountElement。AJAX 章節列表可用 isJSONFormat 處理。

listElement pathElement nameElement reversed sortTypeRawValue subListElement pageCountElement isJSONFormat

request.chapter

{
  "chapter": {
    "main": {
      "settings": { "apiHost": "https://example.com", "apiPath": "/", "httpMethod": "GET" },
      "config":   { "charEncodingRawValue": 2 }
    },
    "isPartial": false
  }
}

response.chapter

{
  "chapter": {
    "listElement": { "cssSelector": ".chapter-list a" },
    "nameElement": { "cssSelector": "self", "actionValue": 0 },
    "pathElement": { "cssSelector": "self", "actionValue": 10, "attributeKey": "href" },
    "reversed": false
  }
}

Content — 章節正文

必填 chapterPathRegexnameElementcontentElement、上一章與下一章路徑。相對檔名如 2.htmlshouldReplaceBookPath 接回書籍路徑;複雜路徑用 isLastPathComponentReplaced + 前後綴重組。

chapterPathRegex contentElement nameElement lastChapterPathElement nextChapterPathElement shouldReplaceBookPath isLastPathComponentReplaced

request.content

{
  "content": {
    "settings": { "apiHost": "https://example.com", "apiPath": "/", "httpMethod": "GET" },
    "config":   { "charEncodingRawValue": 2 }
  }
}

response.content (pTag 正文)

{
  "content": {
    "chapterPathRegex": "^/book/\\d+/\\d+\\.html$",
    "nameElement":    { "cssSelector": "h1.title", "actionValue": 0 },
    "contentElement": {
      "cssSelector": "div.txtnav",
      "actionValue": 22,
      "separator": "\n",
      "removeElements": ["script", "style", ".ad"],
      "invalidTags":   ["<h", "<script"],
      "replaceTags":   ["<p>", "</p>", "<br>", "<br />"]
    },
    "lastChapterPathElement": { "cssSelector": ".prev", "actionValue": 10, "attributeKey": "href" },
    "nextChapterPathElement": { "cssSelector": ".next", "actionValue": 10, "attributeKey": "href" }
  }
}

Category — 分類列表

分類列表需要 pageSize 與列表解析欄位。
沒分頁時於 request 設 shouldIgnorePageIndex: true
第一頁 URL 特殊時使用 apiPathReplacements
pageCount / pageCountElement 都不給時,系統不會依 pageSize 自動續抓,單次解析完成後即停止。

pageSize pageCount? apiPathReplacements listElement pathElement nameElement

request.category

{
  "category": {
    "main": {
      "settings": {
        "apiHost": "https://example.com",
        "apiPath": "/class_1_1.html",
        "apiPathReplacements": { "_1.html": ".html" },
        "apiPageIndexPrefix": "_",
        "apiPathSuffix": ".html",
        "httpMethod": "GET"
      },
      "config": { "charEncodingRawValue": 2 }
    }
  }
}

response.category

{
  "category": {
    "pageSize": 20,
    "listElement": { "cssSelector": ".novel-list li" },
    "nameElement": { "cssSelector": ".name", "actionValue": 0 },
    "pathElement": { "cssSelector": "a",     "actionValue": 10, "attributeKey": "href" },
    "imageElement":{ "cssSelector": "img",   "actionValue": 10, "attributeKey": "src" },
    "authorElement":{ "cssSelector":".author","actionValue": 0 }
  }
}

Search — 搜尋

多筆結果用 mPathElement / mNameElement;自動跳轉到單筆詳細頁時,補 sPathElement / sNameElement 等 s-* 欄位,並用 cssSelector 指定該筆根節點。apiQueryTypeRawValue 決定 query / path 模式。

GET / POST listElement mNameElement mPathElement sNameElement? sPathElement? apiQueryTypeRawValue apiQueryKey

request.search (GET query)

{
  "search": {
    "settings": {
      "apiHost": "https://example.com",
      "apiPath": "/search.html",
      "httpMethod": "GET",
      "apiQueryKey": "q",
      "apiQueryTypeRawValue": 1
    },
    "config": { "charEncodingRawValue": 2 }
  }
}

response.search (多筆 + 單筆跳轉)

{
  "search": {
    "listElement": { "cssSelector": ".result-list li" },
    "mNameElement": { "cssSelector": ".name", "actionValue": 0 },
    "mPathElement": { "cssSelector": "a",     "actionValue": 10, "attributeKey": "href" },
    "cssSelector": "div.book",
    "sNameElement": { "cssSelector": "h1",       "actionValue": 0 },
    "sPathElement": { "cssSelector": "link[rel=canonical]", "actionValue": 10,
                       "attributeKey": "href" }
  }
}
10

常見場景配置#

以下整理範例中常見的配置方式。遇到相同情境時,可先採用相近結構,再依目標站台調整 selector、regex、header 與編碼。

Cloudflare / WebView 驗證

頁面需要瀏覽器環境或挑戰驗證時,在 request config 改用 WebView。
response config 只放用來判斷挑戰與驗證完成的關鍵字。
shouldReload 視站台驗證後是否需要重新載入頁面而定,不是固定必填。

// request.config
{
  "requestMethodRawValue": 1,
  "websiteDataStoreRawValue": 1
}

// response.config
{
  "charEncodingRawValue": 2,
  "challengeKeywords": ["challenge"],
  "challengeVerifiedKeywords": ["cf_clearance"],
  "challengeCookieDomains": [".example.com"],
  "authStorageTypeRawValue": 0,
  "waitForCookie": true
}

AJAX 章節列表(JSON API)

詳細頁可從 meta 或 DOM 取 book id,用 prefix / suffix 拼出章節 API。回 JSON 時用 isJSONFormat+jsonKey+orderedKeys,後續仍用 CSS selector 解析。

{
  "listElement": {
    "isJSONFormat": true,
    "jsonKey": "data",
    "orderedKeys": ["id", "title"],
    "cssSelector": "#json-content > ul > li"
  },
  "pathElement": {
    "cssSelector": "ul > li:nth-of-type(1)",
    "actionValue": 0,
    "prefix": "/book/40459/",
    "suffix": ".html"
  },
  "nameElement": { "cssSelector": "ul > li:nth-of-type(2)", "actionValue": 0 }
}

pTag 正文清理

actionValue: 22 適合段落以 <p> 呈現的頁面。先 removeElements 移除標題、廣告、script,再用 invalidTags + replaceTags 控制輸出。

{
  "contentElement": {
    "cssSelector": "div.txtnav",
    "actionValue": 22,
    "separator": "\n",
    "removeElements": ["script", "style", ".bottem", ".ad"],
    "invalidTags":   ["<h", "<script", "</script", "⊥"],
    "replaceTags":   ["<p>", "</p>", "<br>", "<br />"]
  }
}

brTag 正文(傳統小說頁)

頁面用 <br> 換行時用 actionValue: 19。記得移除站台廣告與固定提示文字。

{
  "contentElement": {
    "cssSelector": "#content",
    "actionValue": 19,
    "separator": "\n",
    "removeElements": ["script", ".adsbygoogle"],
    "blankReplacements": {
      "0": ["請記住本站永久域名", "天才一秒記住"]
    }
  }
}

路徑正規化 + 章節接書路徑

解析結果若包含站台 host,可用 hostsToReplace 移除指定 host 字串,輸出一致的 path 格式。
正文若回相對檔名(如 2.html),用 shouldReplaceBookPath + bookPathReplacements 接回。

{
  "hostsToReplace": [
    "https://www.example.com",
    "http://www.example.com",
    "https://example.com"
  ],
  "content": {
    "chapterPathRegex": "^/book/\\d+/\\d+\\.html$",
    "shouldReplaceBookPath": true,
    "bookPathReplacements": { "/": [".html"] }
  }
}

多分類標籤(actionValue: 2)

多個 label 元素合併成一個字串,並可只取特定索引(如第一個分類)。

{
  "authorElement": {
    "actionValue": 2,
    "cssSelector": ".labelbox label",
    "collectionSeparator": ", ",
    "collectionActionValue": 0,
    "collectionValueIndexes": [0]
  }
}

JS 動態簽名(搜尋加密)

搜尋需 JS 加密簽名時,注入 source 函式並用 invoke 呼叫,結果以 key 為鍵存入查詢字典。keywords 是系統自動帶入的關鍵字。

{
  "jsScripts": [{
    "key": "sig",
    "source": "function encode(s){ return btoa(unescape(encodeURIComponent(s))); }",
    "invoke": "encode(keywords)"
  }]
}

分類無分頁 / 條件忽略分頁

分類沒分頁時忽略索引;只在含特定關鍵字(如 author)時才忽略。

{
  "shouldIgnorePageIndex": true,
  "ignorePageIndexByKeywords": ["author", "tag"]
}
11

範例書源與 Agent 文檔#

此區列出 18 份完整書源範例與 1 份 AI Agent 技能文檔。新增書源時,建議先找一份與目標站台結構接近的範例,對照其 request、response、selector 與 regex 設定。範例檔案位於 examples/

18 個檔案

點擊任一張卡片可預覽內容,並在預覽視窗內複製或下載。

i
範例選用建議 GBK 站台可參考 52書庫全本小說;WebView 可參考 思兔閱讀;AJAX 章節列表可參考 吞噬小说;POST 搜尋可參考 zhaoshuyuan;分頁目錄可參考 明智屋小說
12

最小可用模板#

下方保留必要骨架。建立新書源時,可先替換基本資訊,再依站台補上 selector、regex、編碼、header 與分頁設定。

examples/新書源.json
{
  "info": {
    "type": 99,
    "name": "書源名稱",
    "alias": "書源名稱",
    "index": 1105,
    "version": 1,
    "enabled": true,
    "sortIndex": 1105,
    "allowSearch": true,
    "allowDownload": true
  },
  "data": {
    "searchIndex": 1105,
    "request": {
      "home":     { "main": { "settings": {}, "config": {} }, "inPage": true },
      "detail":   { "main": { "settings": {}, "config": {} }, "inPage": false },
      "chapter":  { "main": { "settings": {}, "config": {} }, "isPartial": false },
      "content":  { "settings": {}, "config": {} },
      "category": { "main": { "settings": {}, "config": {} } },
      "search":   { "settings": {}, "config": {} }
    },
    "response": {
      "config": { "charEncodingRawValue": 2 },
      "hostsToReplace": ["https://example.com"],
      "imageHost": "https://example.com",
      "home":     {},
      "detail":   {},
      "chapter":  {},
      "content":  {},
      "category": {},
      "search":   {}
    }
  }
}
13

疑難排解#

依常見症狀整理可能原因與處理方式。遇到解析或請求異常時,先從對應條目檢查。

列表全部解析成同一筆,或只有第一筆
通常是 listElement 沒指到「重複項目」,而是指到整個容器。
檢查 selector 是否落在每筆共用的父節點上,例如 .book-list li,不要只選 .book-list
{
  "listElement": { "cssSelector": ".book-list li" },
  "nameElement": { "cssSelector": "a", "actionValue": 0 },
  "pathElement": { "cssSelector": "a", "actionValue": 10, "attributeKey": "href" }
}
正文混入廣告、站台提示或 script
先用 removeElements 移除子元素,再用 blankReplacements 清理固定字串。
使用 actionValue: 22 時,可搭配 invalidTagsreplaceTags 統一段落格式。
{
  "contentElement": {
    "cssSelector": "#content",
    "actionValue": 22,
    "separator": "\n",
    "removeElements": ["script", "style", ".ad"],
    "blankReplacements": {
      "1": ["請記住本站.*", "本章未完.*"]
    },
    "invalidTags": ["<script", "<style"],
    "replaceTags": ["<p>", "</p>", "<br>", "<br />"]
  }
}
章節目錄需要分頁載入
若章節目錄分散在多個頁面,需在 request 加分頁參數,並在 response 加 pageCountElement 或固定 pageCount
若 API 回傳 JSON,可先包裝成可用 CSS selector 解析的結構。
{
  "request": {
    "chapter": {
      "main": {
        "settings": {
          "apiHost": "https://example.com",
          "apiPath": "/book/123/",
          "apiPageIndexPrefix": "page_",
          "apiPathSuffix": ".html",
          "httpMethod": "GET"
        },
        "config": { "charEncodingRawValue": 2 }
      },
      "isPartial": true
    }
  },
  "response": {
    "chapter": {
      "pageCount": 5,
      "listElement": { "cssSelector": ".chapter-list a" },
      "nameElement": { "cssSelector": "self", "actionValue": 0 },
      "pathElement": {
        "cssSelector": "self",
        "actionValue": 10,
        "attributeKey": "href"
      }
    }
  }
}
Cloudflare 驗證後仍無法取得內容
先確認該頁是否真的需要 WebView。
需要時,request 使用 requestMethodRawValue: 1
response config 放挑戰判斷與驗證完成關鍵字;shouldReload 只在驗證完成後需要重新載入頁面時才設定。
{
  "request": {
    "detail": {
      "main": {
        "config": {
          "requestMethodRawValue": 1,
          "websiteDataStoreRawValue": 1
        }
      }
    }
  },
  "response": {
    "config": {
      "challengeKeywords": ["challenges.cloudflare", "Just a moment"],
      "challengeVerifiedKeywords": ["cf_clearance"],
      "challengeCookieDomains": [".example.com"],
      "authStorageTypeRawValue": 0,
      "waitForCookie": true
    }
  }
}
搜尋自動跳轉到單本詳細頁,結果空白
補上 s-* 系列欄位,再用 search 根節點 cssSelector 指定該筆容器,讓後續解析與多筆流程一致。
{
  "search": {
    "cssSelector": "div.book",
    "sNameElement": { "cssSelector": "h1", "actionValue": 0 },
    "sPathElement": {
      "cssSelector": "link[rel=canonical]",
      "actionValue": 10,
      "attributeKey": "href"
    }
  }
}
章節順序顛倒(最新章節在最前面)
完整章節目錄順序顛倒時,在 response.chapterreversed: true
partialReversed 只用於分頁章節目錄,表示每一頁內的章節也需要反轉。
若要依章節路徑或章節名稱排序,再搭配 sortTypeRawValue
{
  "chapter": {
    "reversed": true,
    "sortTypeRawValue": 1
  }
}
HTML 結構不完整 / 只有關閉標籤
於對應 ParseElement 設 shouldNormalise: true,系統會先補齊再解析。
若資料是非標準字串,改用 actionValue: 20 + parseableItems 裁切。
{
  "nameElement": {
    "cssSelector": "script",
    "actionValue": 20,
    "parseableItems": [
      { "value": "bookName: '", "direction": 2 },
      { "value": "'", "direction": 1 }
    ]
  }
}
分類第一頁與第二頁 URL 不同
request 用 apiPathReplacements 把第一頁特殊段轉成第二頁起的模式,再讓系統套上 apiPageIndexPrefix + 索引。
若需要等完整 URL 組合後再改,使用 finalURLReplacements
{
  "settings": {
    "apiPathReplacements": {
      "_1.html": ""
    },
    "apiPageIndexPrefix": "_",
    "apiPathSuffix": ".html",
    "finalURLReplacements": {
      "/index.html": "/"
    }
  }
}
14

驗收清單#

完成規則後,逐項確認結構、路徑與解析結果。
先實測詳細、章節與正文;首頁、分類與搜尋依來源支援情況測試。

規則完整性

  • info.index 與既有書源不衝突(建議 1000+)
  • 已覆蓋來源支援的功能區塊;不支援分類或搜尋時可省略
  • hostsToReplacepathRegexchapterPathRegex 對齊最終路徑格式
  • 若支援分類或搜尋,需實測分類分頁與搜尋多筆結果
  • JSON 結構合法,可被 App 正常匯入

解析品質

  • listElement 直接指向重複項目,避免整個容器解析成單筆
  • 正文已移除廣告、script、導覽按鈕與站台提示文字
  • 若支援搜尋,需處理多筆結果;若站台會單筆跳轉,再補 s-* 欄位
  • Cloudflare / AJAX / GBK / 動態參數,request 與 response 對齊
  • 封面 URL 完整可載入(必要時加 imageHost

實測順序建議

  1. 把書源 JSON 匯入 App,確認列表可見且能進入
  2. 若有首頁推薦,確認列表可正常載入與點擊
  3. 若有分類列表,至少測試第二頁或下一頁載入
  4. 若有搜尋,測試多筆關鍵字;若站台會單筆跳轉,再測 s-* 欄位
  5. 進入章節目錄、開啟正文,驗證上一章 / 下一章按鈕
  6. 進入正文頁查看內容是否完整