fix(sites): 按真实列表页修正 OurBits / 彩虹岛 / 憨憨 解析
2026-10-02 用真实账号各抓一次列表页与用户页(只读)核对: - OurBits:标题列嵌套 table.torrentname 有 5 个 td,td:nth-child(n) 先命中嵌套格子, 大小全为 0、发布时间 N/A;改为只匹配行的直接子单元格。副标题原取成标签块,改为标签之后的正文; 新增标签与列表页 IMDb / 豆瓣 ID(label.imdb_rate / douban_rate 的 data 属性) - 彩虹岛:font.subtitle 去掉前面的 div.tag 与末尾徽标,标签单独取 - 憨憨:新增列表标签(span.tag,不含促销) - 纯数字 IMDb ID 补成 tt + 7 位(imdbIdOf) - 用户页核对:下载量为 0 是站点实际显示(VIP),解析正确;上传量四站与页面一致 - 脱敏样例 sites-2026.ts + 定义测试;差分登记 23 条有意偏离(新增 labels、OurBits 副标题) Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
cac1833f53
commit
64e1dfb1d2
12 files changed
+327
-16
No files matched your search
@@ -4,6 +4,12 @@
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### 修复(站点适配,2026-10-02 真实账号列表页核对)
|
||||
- OurBits:标题列嵌套表格让大小、发布时间取错(大小全为 0),改为只匹配行的直接子单元格;副标题原先取成了标签,改为取标签之后的正文;新增标签与列表页 IMDb / 豆瓣 ID。
|
||||
- 彩虹岛:副标题前混入的标签与末尾徽标去掉,标签单独取。
|
||||
- 憨憨:新增列表标签。
|
||||
- 站点给出纯数字 IMDb ID 时补成 `tt` + 7 位。
|
||||
|
||||
## [0.3.15] - 2026-10-02
|
||||
|
||||
### 修复
|
||||
|
||||
@@ -149,7 +149,8 @@
|
||||
}
|
||||
},
|
||||
"description": {
|
||||
"selector": "font.subtitle"
|
||||
"selector": "font.subtitle",
|
||||
"remove": "div"
|
||||
},
|
||||
"category": {
|
||||
"selector": "a[href^='?cat=']",
|
||||
@@ -170,6 +171,9 @@
|
||||
"args": ["\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}", 0]
|
||||
}
|
||||
]
|
||||
},
|
||||
"labels": {
|
||||
"selector": "font.subtitle div.tag"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
import {
|
||||
CHDBITS_LIST_PAGE,
|
||||
CHDBITS_LIST_PAGE_2026,
|
||||
CHDBITS_LOGIN_PAGE,
|
||||
HHANCLUB_DETAIL_PAGE,
|
||||
HHANCLUB_LIST_PAGE,
|
||||
HHANCLUB_LIST_PAGE_2026,
|
||||
HHANCLUB_LIST_PAGE_TAGS,
|
||||
OURBITS_LIST_PAGE,
|
||||
} from "@umt/testkit";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { getSiteDefinition, listSiteDefinitions } from "../registry.ts";
|
||||
import { parseDetailPage } from "../spider/detail.ts";
|
||||
import { spiderTorrentToRelease } from "../spider/map.ts";
|
||||
import { imdbIdOf, spiderTorrentToRelease } from "../spider/map.ts";
|
||||
import type { IndexerDefinition } from "../spider/schema.ts";
|
||||
import { SiteSpider } from "../spider/spider.ts";
|
||||
import {
|
||||
@@ -198,3 +201,44 @@ describe("CHDBits 定义(标准 NexusPHP)", () => {
|
||||
expect(r.torrents).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("2026-10 真实列表页结构(脱敏样例)", () => {
|
||||
const parse = (key: string, html: string) => {
|
||||
const def = BUILTIN_SITE_DEFINITIONS[key] as IndexerDefinition;
|
||||
return new SiteSpider(def, {}).parse(html).torrents.map((t) => spiderTorrentToRelease(t, def, OBSERVED)!);
|
||||
};
|
||||
|
||||
it("OurBits:嵌套 torrentname 不干扰列号;副标题在标签之后;标签与列表页 IMDb / 豆瓣 ID", () => {
|
||||
const [a] = parse("ourbits", OURBITS_LIST_PAGE);
|
||||
expect(a).toMatchObject({
|
||||
siteReleaseId: "900001",
|
||||
sizeBytes: Math.round(38.44 * 1024 ** 3),
|
||||
seeders: 174,
|
||||
leechers: 17,
|
||||
snatched: 334,
|
||||
subtitle: "示例电影[简繁双语字幕]",
|
||||
tags: ["官方", "中字"],
|
||||
imdbId: "tt0299977",
|
||||
doubanId: "1306123",
|
||||
});
|
||||
});
|
||||
|
||||
it("CHDBits:副标题去掉前面的标签块和末尾徽标,标签单独取", () => {
|
||||
const [a] = parse("chdbits", CHDBITS_LIST_PAGE_2026);
|
||||
expect(a!.subtitle).toBe("示例劫案 [DIY UHD 原盘]");
|
||||
expect(a!.tags).toEqual(["官方", "DIY", "中字"]);
|
||||
});
|
||||
|
||||
it("HHClub:标签取 span.tag,不混入促销", () => {
|
||||
const [a] = parse("hhanclub", HHANCLUB_LIST_PAGE_TAGS);
|
||||
expect(a!.tags).toEqual(["官方", "中字"]);
|
||||
});
|
||||
|
||||
it("IMDb ID 规范化:纯数字补 tt + 7 位,0 与非法值丢弃", () => {
|
||||
expect(imdbIdOf("299977")).toBe("tt0299977");
|
||||
expect(imdbIdOf("35522483")).toBe("tt35522483");
|
||||
expect(imdbIdOf("tt0120907")).toBe("tt0120907");
|
||||
expect(imdbIdOf("0")).toBeUndefined();
|
||||
expect(imdbIdOf("N/A")).toBeUndefined();
|
||||
});
|
||||
});
|
||||
@@ -181,6 +181,9 @@
|
||||
},
|
||||
"hr": {
|
||||
"selector": "div.torrent-info-hr"
|
||||
},
|
||||
"labels": {
|
||||
"selector": "div.torrent-title span.tag"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
"ref": "v0.27.0",
|
||||
"commit": "c79bb1b0",
|
||||
"license": "MIT",
|
||||
"file": "src/movieclaw_tracker/sites/configs/ourbits.yaml"
|
||||
"file": "src/movieclaw_tracker/sites/configs/ourbits.yaml",
|
||||
"overrides": "scripts/mp-sync/movieclaw-overrides/ourbits.json"
|
||||
},
|
||||
"domain": "https://ourbits.club/",
|
||||
"encoding": "UTF-8",
|
||||
@@ -105,20 +106,20 @@
|
||||
"attribute": "href"
|
||||
},
|
||||
"imdbid": {
|
||||
"selector": "a[href*=\"imdb.com/title/tt\"]",
|
||||
"attribute": "href",
|
||||
"selector": "label.imdb_rate",
|
||||
"attribute": "data-imdbid",
|
||||
"filters": [
|
||||
{
|
||||
"name": "re_search",
|
||||
"args": ["tt\\d+", 0]
|
||||
"args": ["[1-9]\\d*", 0]
|
||||
}
|
||||
]
|
||||
},
|
||||
"date_elapsed": {
|
||||
"selector": "td:nth-child(4)"
|
||||
"selector": "> td:nth-child(4)"
|
||||
},
|
||||
"date_added": {
|
||||
"selector": "td:nth-child(4) > span",
|
||||
"selector": "> td:nth-child(4) > span",
|
||||
"attribute": "title"
|
||||
},
|
||||
"date": {
|
||||
@@ -131,16 +132,16 @@
|
||||
]
|
||||
},
|
||||
"size": {
|
||||
"selector": "td:nth-child(5)"
|
||||
"selector": "> td:nth-child(5)"
|
||||
},
|
||||
"seeders": {
|
||||
"selector": "td:nth-child(6)"
|
||||
"selector": "> td:nth-child(6)"
|
||||
},
|
||||
"leechers": {
|
||||
"selector": "td:nth-child(7)"
|
||||
"selector": "> td:nth-child(7)"
|
||||
},
|
||||
"grabs": {
|
||||
"selector": "td:nth-child(8)"
|
||||
"selector": "> td:nth-child(8)"
|
||||
},
|
||||
"downloadvolumefactor": {
|
||||
"case": {
|
||||
@@ -161,7 +162,8 @@
|
||||
}
|
||||
},
|
||||
"description": {
|
||||
"selector": "td.embedded > div"
|
||||
"selector": "table.torrentname td.embedded",
|
||||
"remove": "a, b, img, div, br, font"
|
||||
},
|
||||
"category": {
|
||||
"selector": "a[href^='?cat=']",
|
||||
@@ -175,6 +177,19 @@
|
||||
},
|
||||
"hr": {
|
||||
"selector": "img.hitandrun"
|
||||
},
|
||||
"labels": {
|
||||
"selector": "span.tag"
|
||||
},
|
||||
"doubanid": {
|
||||
"selector": "label.douban_rate",
|
||||
"attribute": "data-doubanid",
|
||||
"filters": [
|
||||
{
|
||||
"name": "re_search",
|
||||
"args": ["[1-9]\\d*", 0]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -45,6 +45,14 @@ export function categoryName(def: IndexerDefinition, id: string | undefined): st
|
||||
}
|
||||
|
||||
/** SpiderTorrent(MP TorrentInfo 字段)→ SiteRelease。raw 保存完整 MP 字段(enclosure 脱敏)。 */
|
||||
/** 站点给的 IMDb ID:`tt0299977` 原样;纯数字(OurBits 列表页 data-imdbid)补成 `tt` + 至少 7 位;其他丢弃 */
|
||||
export function imdbIdOf(v: string | undefined): string | undefined {
|
||||
const s = v?.trim();
|
||||
if (!s) return undefined;
|
||||
if (/^tt\d{7,}$/.test(s)) return s;
|
||||
return /^\d+$/.test(s) && Number(s) > 0 ? `tt${s.padStart(7, "0")}` : undefined;
|
||||
}
|
||||
|
||||
export function spiderTorrentToRelease(
|
||||
t: SpiderTorrent,
|
||||
def: IndexerDefinition,
|
||||
@@ -77,7 +85,8 @@ export function spiderTorrentToRelease(
|
||||
observedAt,
|
||||
};
|
||||
if (t.description) release.subtitle = t.description;
|
||||
if (t.imdbid) release.imdbId = t.imdbid;
|
||||
const imdb = imdbIdOf(t.imdbid);
|
||||
if (imdb) release.imdbId = imdb;
|
||||
if (t.doubanid) release.doubanId = t.doubanid;
|
||||
return release;
|
||||
}
|
||||
@@ -7333,6 +7333,98 @@
|
||||
"tjupt@tk-chdbits-list#1.pubdate": {
|
||||
"kind": "mp_bug",
|
||||
"note": "tjupt 定义 date_added 取单元格文本(\"2天\"/\"26天\"相对时间),MP normalize_datetime 用 dateparser 以当前时刻换算成绝对时间(生成时冻结 now=2026-09-27T12:00),结果随抓取时刻漂移;本项目 normalizeDatetime 只接受绝对时间,相对时间不写 pubdate(与 MP __is_relative_pubdate_text 的意图一致)。"
|
||||
},
|
||||
"chdbits@tk-audiences-list#0.labels": {
|
||||
"kind": "intentional",
|
||||
"note": "2026-10-02 按真实列表页给 OurBits / 彩虹岛 / 憨憨 覆盖层新增 labels 字段(MovieClaw 原定义没有,MP 参照因此为 undefined);样例里没有标签时本项目输出空数组。"
|
||||
},
|
||||
"chdbits@tk-audiences-list#1.labels": {
|
||||
"kind": "intentional",
|
||||
"note": "2026-10-02 按真实列表页给 OurBits / 彩虹岛 / 憨憨 覆盖层新增 labels 字段(MovieClaw 原定义没有,MP 参照因此为 undefined);样例里没有标签时本项目输出空数组。"
|
||||
},
|
||||
"chdbits@tk-audiences-list#2.labels": {
|
||||
"kind": "intentional",
|
||||
"note": "2026-10-02 按真实列表页给 OurBits / 彩虹岛 / 憨憨 覆盖层新增 labels 字段(MovieClaw 原定义没有,MP 参照因此为 undefined);样例里没有标签时本项目输出空数组。"
|
||||
},
|
||||
"chdbits@tk-audiences-list#3.labels": {
|
||||
"kind": "intentional",
|
||||
"note": "2026-10-02 按真实列表页给 OurBits / 彩虹岛 / 憨憨 覆盖层新增 labels 字段(MovieClaw 原定义没有,MP 参照因此为 undefined);样例里没有标签时本项目输出空数组。"
|
||||
},
|
||||
"chdbits@tk-audiences-list#4.labels": {
|
||||
"kind": "intentional",
|
||||
"note": "2026-10-02 按真实列表页给 OurBits / 彩虹岛 / 憨憨 覆盖层新增 labels 字段(MovieClaw 原定义没有,MP 参照因此为 undefined);样例里没有标签时本项目输出空数组。"
|
||||
},
|
||||
"chdbits@tk-chdbits-list#0.labels": {
|
||||
"kind": "intentional",
|
||||
"note": "2026-10-02 按真实列表页给 OurBits / 彩虹岛 / 憨憨 覆盖层新增 labels 字段(MovieClaw 原定义没有,MP 参照因此为 undefined);样例里没有标签时本项目输出空数组。"
|
||||
},
|
||||
"chdbits@tk-chdbits-list#1.labels": {
|
||||
"kind": "intentional",
|
||||
"note": "2026-10-02 按真实列表页给 OurBits / 彩虹岛 / 憨憨 覆盖层新增 labels 字段(MovieClaw 原定义没有,MP 参照因此为 undefined);样例里没有标签时本项目输出空数组。"
|
||||
},
|
||||
"hhanclub@tk-hhanclub-list#0.labels": {
|
||||
"kind": "intentional",
|
||||
"note": "2026-10-02 按真实列表页给 OurBits / 彩虹岛 / 憨憨 覆盖层新增 labels 字段(MovieClaw 原定义没有,MP 参照因此为 undefined);样例里没有标签时本项目输出空数组。"
|
||||
},
|
||||
"hhanclub@tk-hhanclub-list#1.labels": {
|
||||
"kind": "intentional",
|
||||
"note": "2026-10-02 按真实列表页给 OurBits / 彩虹岛 / 憨憨 覆盖层新增 labels 字段(MovieClaw 原定义没有,MP 参照因此为 undefined);样例里没有标签时本项目输出空数组。"
|
||||
},
|
||||
"ourbits@tk-audiences-list#0.description": {
|
||||
"kind": "intentional",
|
||||
"note": "2026-10-02 OurBits 覆盖层改了副标题选择器(原定义 td.embedded > div 取到的是标签块,真实页面副标题在标签之后);MP 参照仍用原定义,在通用样例上为 null,本项目取到正文。"
|
||||
},
|
||||
"ourbits@tk-audiences-list#0.labels": {
|
||||
"kind": "intentional",
|
||||
"note": "2026-10-02 按真实列表页给 OurBits / 彩虹岛 / 憨憨 覆盖层新增 labels 字段(MovieClaw 原定义没有,MP 参照因此为 undefined);样例里没有标签时本项目输出空数组。"
|
||||
},
|
||||
"ourbits@tk-audiences-list#1.description": {
|
||||
"kind": "intentional",
|
||||
"note": "2026-10-02 OurBits 覆盖层改了副标题选择器(原定义 td.embedded > div 取到的是标签块,真实页面副标题在标签之后);MP 参照仍用原定义,在通用样例上为 null,本项目取到正文。"
|
||||
},
|
||||
"ourbits@tk-audiences-list#1.labels": {
|
||||
"kind": "intentional",
|
||||
"note": "2026-10-02 按真实列表页给 OurBits / 彩虹岛 / 憨憨 覆盖层新增 labels 字段(MovieClaw 原定义没有,MP 参照因此为 undefined);样例里没有标签时本项目输出空数组。"
|
||||
},
|
||||
"ourbits@tk-audiences-list#2.description": {
|
||||
"kind": "intentional",
|
||||
"note": "2026-10-02 OurBits 覆盖层改了副标题选择器(原定义 td.embedded > div 取到的是标签块,真实页面副标题在标签之后);MP 参照仍用原定义,在通用样例上为 null,本项目取到正文。"
|
||||
},
|
||||
"ourbits@tk-audiences-list#2.labels": {
|
||||
"kind": "intentional",
|
||||
"note": "2026-10-02 按真实列表页给 OurBits / 彩虹岛 / 憨憨 覆盖层新增 labels 字段(MovieClaw 原定义没有,MP 参照因此为 undefined);样例里没有标签时本项目输出空数组。"
|
||||
},
|
||||
"ourbits@tk-audiences-list#3.description": {
|
||||
"kind": "intentional",
|
||||
"note": "2026-10-02 OurBits 覆盖层改了副标题选择器(原定义 td.embedded > div 取到的是标签块,真实页面副标题在标签之后);MP 参照仍用原定义,在通用样例上为 null,本项目取到正文。"
|
||||
},
|
||||
"ourbits@tk-audiences-list#3.labels": {
|
||||
"kind": "intentional",
|
||||
"note": "2026-10-02 按真实列表页给 OurBits / 彩虹岛 / 憨憨 覆盖层新增 labels 字段(MovieClaw 原定义没有,MP 参照因此为 undefined);样例里没有标签时本项目输出空数组。"
|
||||
},
|
||||
"ourbits@tk-audiences-list#4.description": {
|
||||
"kind": "intentional",
|
||||
"note": "2026-10-02 OurBits 覆盖层改了副标题选择器(原定义 td.embedded > div 取到的是标签块,真实页面副标题在标签之后);MP 参照仍用原定义,在通用样例上为 null,本项目取到正文。"
|
||||
},
|
||||
"ourbits@tk-audiences-list#4.labels": {
|
||||
"kind": "intentional",
|
||||
"note": "2026-10-02 按真实列表页给 OurBits / 彩虹岛 / 憨憨 覆盖层新增 labels 字段(MovieClaw 原定义没有,MP 参照因此为 undefined);样例里没有标签时本项目输出空数组。"
|
||||
},
|
||||
"ourbits@tk-chdbits-list#0.description": {
|
||||
"kind": "intentional",
|
||||
"note": "2026-10-02 OurBits 覆盖层改了副标题选择器(原定义 td.embedded > div 取到的是标签块,真实页面副标题在标签之后);MP 参照仍用原定义,在通用样例上为 null,本项目取到正文。"
|
||||
},
|
||||
"ourbits@tk-chdbits-list#0.labels": {
|
||||
"kind": "intentional",
|
||||
"note": "2026-10-02 按真实列表页给 OurBits / 彩虹岛 / 憨憨 覆盖层新增 labels 字段(MovieClaw 原定义没有,MP 参照因此为 undefined);样例里没有标签时本项目输出空数组。"
|
||||
},
|
||||
"ourbits@tk-chdbits-list#1.description": {
|
||||
"kind": "intentional",
|
||||
"note": "2026-10-02 OurBits 覆盖层改了副标题选择器(原定义 td.embedded > div 取到的是标签块,真实页面副标题在标签之后);MP 参照仍用原定义,在通用样例上为 null,本项目取到正文。"
|
||||
},
|
||||
"ourbits@tk-chdbits-list#1.labels": {
|
||||
"kind": "intentional",
|
||||
"note": "2026-10-02 按真实列表页给 OurBits / 彩虹岛 / 憨憨 覆盖层新增 labels 字段(MovieClaw 原定义没有,MP 参照因此为 undefined);样例里没有标签时本项目输出空数组。"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from "./chdbits.ts";
|
||||
export * from "./hhanclub.ts";
|
||||
export * from "./sites-2026.ts";
|
||||
export * from "./subtitles.ts";
|
||||
@@ -0,0 +1,70 @@
|
||||
/**
|
||||
* 2026-10-02 按真实账号列表页(OurBits / CHDBits / HHClub 各 1 次只读抓取)的 DOM 结构手写的脱敏样例:
|
||||
* 标题、id、ID 号与数值均为虚构,只保留会影响解析的结构(嵌套 table.torrentname、标签块、外部 ID 属性)。
|
||||
*/
|
||||
|
||||
/** OurBits:标题列嵌套的 table.torrentname 有 5 个 td;副标题在标签 div 之后;IMDb 为纯数字 data 属性 */
|
||||
export const OURBITS_LIST_PAGE = `<!DOCTYPE html><html><head><title>OurBits :: 种子</title></head><body>
|
||||
<a href="logout.php">退出</a>
|
||||
<table class="torrents" id="torrenttable"><tbody>
|
||||
<tr><td class="colhead">类型</td><td class="colhead">标题</td><td class="colhead"><img title="评论数"></td><td class="colhead"><img title="存活时间"></td><td class="colhead"><img title="大小"></td><td class="colhead"><img title="种子数"></td><td class="colhead"><img title="下载数"></td><td class="colhead"><img title="完成数"></td><td class="colhead">进度</td><td class="colhead">发布者</td></tr>
|
||||
<tr>
|
||||
<td class="rowfollow"><a href="?cat=401"><img class="c_movie" alt="Movies" title="Movies"></a></td>
|
||||
<td class="rowfollow"><table class="torrentname"><tbody><tr class="sticky_top_more">
|
||||
<td class="embedded"><img src="pic/sticky3.png" title="高级置顶"> <a title="Sample Film 2002 1080p BluRay AVC DTS-HD MA 5.1-GRP@OurBits" href="details.php?id=900001&hit=1"><b>Sample Film 2002 1080p BluRay AVC DTS-HD MA 5.1-GRP@OurBits</b></a> <b>[<font class="free">免费</font>]</b><br><div style="display:inline-block"><a href="?tags[]=gf"><span class="tag tag-gf">官方</span></a><a href="?tags[]=zz"><span class="tag tag-zz">中字</span></a></div>示例电影[简繁双语字幕]</td>
|
||||
<td class="embedded" width="30px"><div></div></td>
|
||||
<td class="embedded" width="20px"><div><img title="暂未审核"></div></td>
|
||||
<td class="embedded" width="50px"><div><em class="imdbImg"><label class="imdb_rate" data-imdbid="299977">N/A</label></em></div><div><em class="doubanImg"><label class="douban_rate" data-doubanid="1306123">N/A</label></em></div></td>
|
||||
<td class="embedded"><a href="download.php?id=900001"><img class="download" title="下载本种"></a></td>
|
||||
</tr></tbody></table></td>
|
||||
<td class="rowfollow">2</td>
|
||||
<td class="rowfollow"><span title="2026-09-25 08:00:00">9时37分</span></td>
|
||||
<td class="rowfollow">38.44GB</td>
|
||||
<td class="rowfollow">174</td>
|
||||
<td class="rowfollow">17</td>
|
||||
<td class="rowfollow">334</td>
|
||||
<td class="rowfollow">-</td>
|
||||
<td class="rowfollow">匿名</td>
|
||||
</tr>
|
||||
</tbody></table></body></html>`;
|
||||
|
||||
/** CHDBits:font.subtitle 里先是 div.tag 标签,再是副标题,末尾 div.circle 徽标 */
|
||||
export const CHDBITS_LIST_PAGE_2026 = `<!DOCTYPE html><html><head><title>CHDBits :: 种子</title></head><body>
|
||||
<a href="logout.php">退出</a>
|
||||
<table class="torrents"><tbody>
|
||||
<tr><td class="colhead">类型</td><td class="colhead">标题</td></tr>
|
||||
<tr>
|
||||
<td class="rowfollow"><a href="?cat=401"><img alt="Movie"></a></td>
|
||||
<td class="rowfollow"><table class="torrentname"><tbody><tr class="free_bg">
|
||||
<td class="embedded"><a title="Sample Heist 2026 2160p BluRay HEVC DV-GRP@CHDBits" href="details.php?id=900101&hit=1"><b>Sample Heist 2026 2160p BluRay HEVC DV-GRP@CHDBits</b></a> <img class="pro_free" alt="Free" title="免费"> <br> <font class="subtitle"> <div style="display:inline-block"><div class="tag tag-gf">官方</div><div class="tag tag-diy">DIY</div><div class="tag tag-sub">中字</div></div> 示例劫案 [DIY UHD 原盘] <div class="circle"><div class="circle-text">h5</div></div></font></td>
|
||||
<td class="embedded"><a href="download.php?id=900101"><img class="download" title="下载本种"></a></td>
|
||||
</tr></tbody></table></td>
|
||||
<td class="rowfollow">0</td>
|
||||
<td class="rowfollow"><span title="2026-09-25 08:00:00">5时57分</span></td>
|
||||
<td class="rowfollow">88.67GB</td>
|
||||
<td class="rowfollow">12</td>
|
||||
<td class="rowfollow">3</td>
|
||||
<td class="rowfollow">40</td>
|
||||
</tr>
|
||||
</tbody></table></body></html>`;
|
||||
|
||||
/** HHClub 2026 卡片布局:标签在 torrent-title 里的 span.tag(促销是 promotion-tag) */
|
||||
export const HHANCLUB_LIST_PAGE_TAGS = `<!DOCTYPE html><html><head><title>HHCLUB :: 种子</title></head><body>
|
||||
<a href="logout.php">退出</a>
|
||||
<div class="torrent-table-sub-info">
|
||||
<div class="torrent-cat"><a href='?cat[]=402'><img alt='类型'></a></div>
|
||||
<div class="flex torrent-table-for-spider-info torrent-title">
|
||||
<div><a href='details.php?id=900201&hit=1' class='torrent-info-text-name'>Sample Show S02 2025 2160p WEB-DL H.265 DDP5.1-HHWEB</a>
|
||||
<div><div class='torrent-info-text-small_name'>示例剧集 第二季 | 全12集</div></div>
|
||||
<div><a href="?tag_id3=1"><span class="tag">官方</span></a><a href="?tag_id6=1"><span class="tag">中字</span></a><span class="promotion-tag promotion-tag-free">免费</span></div></div>
|
||||
</div>
|
||||
<div class="torrent-info">
|
||||
<div class="torrent-info-text torrent-info-text-size"> 41.05 GB </div>
|
||||
<div class="torrent-info-text torrent-info-text-added"><span title="2026-09-25 08:00:00">9时18分钟</span></div>
|
||||
<div class="torrent-info-text torrent-info-text-seeders"><a href="details.php?id=900201&hit=1&dllist=1#seeders">102</a></div>
|
||||
<div class="torrent-info-text torrent-info-text-leechers"><a href="details.php?id=900201&hit=1&dllist=1#leechers">7</a></div>
|
||||
<div class="torrent-info-text torrent-info-text-finished"><a href='/viewsnatches.php?id=900201'>166</a></div>
|
||||
</div>
|
||||
<div class="torrent-manage"><a href="download.php?id=900201">下载</a></div>
|
||||
</div>
|
||||
</body></html>`;
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"$comment": "CHDBits 分类名取自 MovieClaw chdbits.yaml 注释(TV Series / TV Shows / Sports / Music Videos / HQ Audio)。",
|
||||
"$comment": "CHDBits 分类名取自 MovieClaw chdbits.yaml 注释(TV Series / TV Shows / Sports / Music Videos / HQ Audio)。 2026-10-02 真实列表页核对:font.subtitle 里先是 div.tag 标签再是副标题,末尾 div.circle 徽标;副标题去掉 div,标签取 div.tag。",
|
||||
"umt": {
|
||||
"categoryNames": {
|
||||
"401": "电影",
|
||||
@@ -12,5 +12,16 @@
|
||||
"408": "HQ Audio",
|
||||
"409": "其他"
|
||||
}
|
||||
},
|
||||
"torrents": {
|
||||
"fields": {
|
||||
"description": {
|
||||
"selector": "font.subtitle",
|
||||
"remove": "div"
|
||||
},
|
||||
"labels": {
|
||||
"selector": "font.subtitle div.tag"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"$comment": "HHClub 分类名取自 MovieClaw hhanclub.yaml 注释(导航栏分类表);REQ §4.5 要求综艺/体育/短剧保留原始分类。2026-09-29 真实账号核对:列表标题链接里带站点的新种标记「[新]」(详情页标题没有),用 replace 过滤掉;等级「贵宾」为 VIP;访问默认按低频原则收紧(快照只抓第 1 页、健康检测 6 小时、详情缓存 24 小时、搜索并发 1)。",
|
||||
"$comment": "HHClub 分类名取自 MovieClaw hhanclub.yaml 注释(导航栏分类表);REQ §4.5 要求综艺/体育/短剧保留原始分类。2026-09-29 真实账号核对:列表标题链接里带站点的新种标记「[新]」(详情页标题没有),用 replace 过滤掉;等级「贵宾」为 VIP;访问默认按低频原则收紧(快照只抓第 1 页、健康检测 6 小时、详情缓存 24 小时、搜索并发 1)。 2026-10-02 真实列表页核对:标签在 torrent-title 里的 span.tag(促销是 promotion-tag,不混入)。",
|
||||
"umt": {
|
||||
"categoryNames": {
|
||||
"401": "电影",
|
||||
@@ -48,6 +48,9 @@
|
||||
"args": ["(新)", ""]
|
||||
}
|
||||
]
|
||||
},
|
||||
"labels": {
|
||||
"selector": "div.torrent-title span.tag"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
{
|
||||
"$comment": "2026-10-02 真实账号核对列表页 1 次(.local/site-samples/ourbits-torrents.html):标题列嵌套的 table.torrentname 有 5 个 td,MovieClaw 的 td:nth-child(n) 会先命中嵌套格子(大小 0、时间 N/A),改为只匹配行的直接子单元格;副标题在 td.embedded 里标签 div 之后,原定义取到的是标签;标签在 span.tag;列表页有 IMDb / 豆瓣 ID(label.imdb_rate / label.douban_rate 的 data 属性,IMDb 为纯数字,映射时补 tt + 7 位)。",
|
||||
"torrents": {
|
||||
"fields": {
|
||||
"date_elapsed": {
|
||||
"selector": "> td:nth-child(4)"
|
||||
},
|
||||
"date_added": {
|
||||
"selector": "> td:nth-child(4) > span",
|
||||
"attribute": "title"
|
||||
},
|
||||
"size": {
|
||||
"selector": "> td:nth-child(5)"
|
||||
},
|
||||
"seeders": {
|
||||
"selector": "> td:nth-child(6)"
|
||||
},
|
||||
"leechers": {
|
||||
"selector": "> td:nth-child(7)"
|
||||
},
|
||||
"grabs": {
|
||||
"selector": "> td:nth-child(8)"
|
||||
},
|
||||
"description": {
|
||||
"selector": "table.torrentname td.embedded",
|
||||
"remove": "a, b, img, div, br, font"
|
||||
},
|
||||
"labels": {
|
||||
"selector": "span.tag"
|
||||
},
|
||||
"imdbid": {
|
||||
"selector": "label.imdb_rate",
|
||||
"attribute": "data-imdbid",
|
||||
"filters": [
|
||||
{
|
||||
"name": "re_search",
|
||||
"args": ["[1-9]\\d*", 0]
|
||||
}
|
||||
]
|
||||
},
|
||||
"doubanid": {
|
||||
"selector": "label.douban_rate",
|
||||
"attribute": "data-doubanid",
|
||||
"filters": [
|
||||
{
|
||||
"name": "re_search",
|
||||
"args": ["[1-9]\\d*", 0]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user