1
This commit is contained in:
@@ -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,
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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() {
|
||||
|
||||
Reference in New Issue
Block a user