This commit is contained in:
张成
2026-03-18 09:45:00 +08:00
parent 3c7b629606
commit bc7c2c81ba
4 changed files with 115 additions and 12 deletions

View File

@@ -6,21 +6,38 @@ import { injected_amazon_search_list } from '../injected/amazon_search_list.js';
export function amazon_search_list(data, sendResponse) {
return new Promise(async (resolve, reject) => {
const category_keyword = (data && data.category_keyword) ? String(data.category_keyword).trim() : '';
const sort_by = (data && data.sort_by) ? String(data.sort_by).trim() : '';
const limit = (() => {
const n = data && Object.prototype.hasOwnProperty.call(data, 'limit') ? Number(data.limit) : 100;
if (!Number.isFinite(n)) return 100;
return Math.max(1, Math.min(200, Math.floor(n)));
})();
const keyword = category_keyword || '野餐包';
// 用你给的 URL 作为模板,只替换 k 参数,其它参数保持一致
const sort_map = {
featured: 'relevanceblender',
review: 'review-rank',
newest: 'date-desc-rank',
price_asc: 'price-asc-rank',
price_desc: 'price-desc-rank',
bestseller: 'exact-aware-popularity-rank',
};
const sort_s = Object.prototype.hasOwnProperty.call(sort_map, sort_by) ? sort_map[sort_by] : '';
// 内置 URL只替换 k / s 参数,其它参数保持一致
const default_url = (() => {
const u = new URL('https://www.amazon.com/s');
u.searchParams.set('k', keyword);
u.searchParams.set('__mk_zh_CN', '亚马逊网站');
u.searchParams.set('crid', 'ZKNCI4U8BBAP');
u.searchParams.set('sprefix', '野餐bao,caps,388');
u.searchParams.set('ref', 'nb_sb_noss');
if (sort_s) u.searchParams.set('s', sort_s);
else u.searchParams.delete('s');
return u.toString();
})();
const url = (data && data.url) ? String(data.url).trim() : default_url;
const url = default_url;
let times = 0;
@@ -39,17 +56,67 @@ export function amazon_search_list(data, sendResponse) {
try {
const tab = await tab_task.open_async();
const injected_result_list = await tab.execute_script(injected_amazon_search_list, [{ url, category_keyword }], 'document_idle');
const injected_result = Array.isArray(injected_result_list) ? injected_result_list[0] : null;
const wait_tab_complete = (tab_id) => new Promise((resolve_wait, reject_wait) => {
const on_updated = (updated_tab_id, change_info, updated_tab) => {
if (updated_tab_id !== tab_id) return;
if (change_info.status !== 'complete') return;
chrome.tabs.onUpdated.removeListener(on_updated);
resolve_wait(updated_tab);
};
chrome.tabs.onUpdated.addListener(on_updated);
setTimeout(() => {
chrome.tabs.onUpdated.removeListener(on_updated);
reject_wait(new Error('等待页面加载超时'));
}, 45000);
});
const unique_map = new Map();
let next_url = url;
let page = 1;
while (next_url && unique_map.size < limit) {
await new Promise((resolve_nav, reject_nav) => {
chrome.tabs.update(tab.id, { url: next_url, active: true }, () => {
if (chrome.runtime.lastError) return reject_nav(new Error(chrome.runtime.lastError.message));
resolve_nav(true);
});
});
await wait_tab_complete(tab.id);
const injected_result_list = await tab.execute_script(
injected_amazon_search_list,
[{ url: next_url, category_keyword, sort_by }],
'document_idle',
);
const injected_result = Array.isArray(injected_result_list) ? injected_result_list[0] : null;
const items = injected_result && Array.isArray(injected_result.items) ? injected_result.items : [];
items.forEach((it) => {
const k = (it && (it.asin || it.url)) ? String(it.asin || it.url) : null;
if (!k) return;
if (!unique_map.has(k)) unique_map.set(k, it);
});
next_url = injected_result && injected_result.next_url ? String(injected_result.next_url) : null;
page += 1;
if (page > 10) break; // 防止死循环(默认 100 条一般 <= 3 页)
}
const injected_result = {
stage: 'list',
limit,
total: unique_map.size,
items: Array.from(unique_map.values()).slice(0, limit),
};
const result = {
code: 0,
status: true,
message: 'ok',
data: { tab_id: tab.id, url, category_keyword, result: injected_result },
data: { tab_id: tab.id, url, category_keyword, sort_by: sort_by || 'featured', limit, result: injected_result },
};
send_action('amazon_search_list', result);
resolve({ tab_id: tab.id, url, category_keyword, result: injected_result });
resolve({ tab_id: tab.id, url, category_keyword, sort_by: sort_by || 'featured', limit, result: injected_result });
// 成功后关闭打开的 tab同时会关闭 popup window
tab.remove(0);
@@ -69,16 +136,21 @@ export function amazon_search_list(data, sendResponse) {
amazon_search_list.desc = 'Amazon 搜索结果列表抓取DOM 解析)';
amazon_search_list.params = {
url: {
type: 'string',
desc: '可选,传入完整搜索 URL不传则按 category_keyword 生成)',
default: '',
},
category_keyword: {
type: 'string',
desc: '分类关键词',
default: '野餐包',
},
sort_by: {
type: 'string',
desc: '排序方式featured(精选) / price_asc(价格从低到高) / price_desc(价格从高到低) / review(平均买家评论数) / newest(最新商品) / bestseller(畅销商品)',
default: 'featured',
},
limit: {
type: 'number',
desc: '抓取数量上限(默认 100最大 200',
default: 100,
},
};

View File

@@ -3,6 +3,7 @@
export function injected_amazon_search_list(params) {
const start_url = params && params.url ? String(params.url) : location.href;
const category_keyword = params && params.category_keyword ? String(params.category_keyword).trim() : '';
const sort_by = params && params.sort_by ? String(params.sort_by).trim() : '';
function pick_number(text) {
if (!text) return null;
@@ -93,14 +94,28 @@ export function injected_amazon_search_list(params) {
return items;
}
function pick_next_url() {
const a = document.querySelector('a.s-pagination-next');
if (!a) return null;
const aria_disabled = (a.getAttribute('aria-disabled') || '').trim().toLowerCase();
if (aria_disabled === 'true') return null;
if (a.classList && a.classList.contains('s-pagination-disabled')) return null;
const href = a.getAttribute('href');
if (!href) return null;
return abs_url(href);
}
const items = extract_results();
const next_url = pick_next_url();
// 只返回一次结果,避免 send 太多影响判定
return {
start_url,
href: location.href,
category_keyword,
sort_by,
total: items.length,
items,
next_url,
};
}

View File

@@ -28,6 +28,7 @@
</select>
<label class="label">参数JSON</label>
<div id="action_params_desc" class="hint" style="margin-top:6px; white-space:pre-wrap;"></div>
<textarea id="action_params" class="textarea" spellcheck="false">{}</textarea>
<div class="row">

View File

@@ -1,4 +1,5 @@
const action_name_el = document.getElementById('action_name');
const action_params_desc_el = document.getElementById('action_params_desc');
const action_params_el = document.getElementById('action_params');
const btn_run_el = document.getElementById('btn_run');
const btn_clear_el = document.getElementById('btn_clear');
@@ -111,6 +112,20 @@ function refresh_params_editor() {
if (!meta) return;
const defaults = build_default_params(meta.params);
action_params_el.value = JSON.stringify(defaults, null, 2);
if (action_params_desc_el) {
const schema = meta.params && typeof meta.params === 'object' ? meta.params : {};
const lines = [];
Object.keys(schema).forEach((k) => {
const item = schema[k] || {};
const t = item.type ? String(item.type) : 'any';
const d = item.desc ? String(item.desc) : '';
const defv = Object.prototype.hasOwnProperty.call(item, 'default') ? item.default : undefined;
const def_text = defv === undefined ? '' : `,默认:${JSON.stringify(defv)}`;
lines.push(`- ${k} (${t})${d ? '' + d : ''}${def_text}`);
});
action_params_desc_el.textContent = lines.length ? lines.join('\n') : '(无参数说明)';
}
}
function refresh_action_select() {