feat: 开发采集插件
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
* 文本提取 - 标题、价格、参数表、描述
|
||||
* 从 docs/extension/plan.md §6.6 移植(简化版)
|
||||
*/
|
||||
import type { SiteProfile, TextRule } from '../profiles/types';
|
||||
|
||||
export interface TextMaterial {
|
||||
kind: TextRule['kind'];
|
||||
content: string;
|
||||
pairs?: Array<{ key: string; value: string }>; // table 模式的结构化结果
|
||||
}
|
||||
|
||||
function clean(s: string): string {
|
||||
return s.replace(/\s+/g, ' ').trim();
|
||||
}
|
||||
|
||||
function extractOne(rule: TextRule): TextMaterial | null {
|
||||
for (const sel of rule.selectors) {
|
||||
let nodes: NodeListOf<Element>;
|
||||
try {
|
||||
nodes = document.querySelectorAll(sel);
|
||||
} catch {
|
||||
continue;
|
||||
}
|
||||
if (!nodes.length) continue;
|
||||
|
||||
// table 模式:参数表
|
||||
if (rule.extract === 'table') {
|
||||
const pairs: Array<{ key: string; value: string }> = [];
|
||||
nodes.forEach(row => {
|
||||
const k = clean(row.querySelector(rule.tableKeySelector ?? '')?.textContent ?? '');
|
||||
const v = clean(row.querySelector(rule.tableValueSelector ?? '')?.textContent ?? '');
|
||||
if (k && v) pairs.push({ key: k.replace(/[::]$/, ''), value: v });
|
||||
});
|
||||
if (pairs.length) {
|
||||
return {
|
||||
kind: rule.kind,
|
||||
content: pairs.map(p => `${p.key}: ${p.value}`).join('\n'),
|
||||
pairs
|
||||
};
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// join 模式:标题被拆成多个 span
|
||||
if (rule.extract === 'join') {
|
||||
let text = '';
|
||||
nodes.forEach(n => { text += n.textContent ?? ''; });
|
||||
text = clean(text);
|
||||
if (text) return { kind: rule.kind, content: text };
|
||||
continue;
|
||||
}
|
||||
|
||||
// first 模式:只取第一个
|
||||
const first = clean(nodes[0].textContent ?? '');
|
||||
if (first) return { kind: rule.kind, content: first };
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function collectTexts(profile: SiteProfile): {
|
||||
materials: TextMaterial[];
|
||||
missingRequired: string[];
|
||||
} {
|
||||
const materials: TextMaterial[] = [];
|
||||
const missingRequired: string[] = [];
|
||||
|
||||
for (const rule of profile.textRules) {
|
||||
const m = extractOne(rule);
|
||||
if (m) materials.push(m);
|
||||
else if (rule.required) missingRequired.push(rule.kind);
|
||||
}
|
||||
|
||||
return { materials, missingRequired };
|
||||
}
|
||||
Reference in New Issue
Block a user