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) {
|
export function amazon_search_list(data, sendResponse) {
|
||||||
return new Promise(async (resolve, reject) => {
|
return new Promise(async (resolve, reject) => {
|
||||||
const category_keyword = (data && data.category_keyword) ? String(data.category_keyword).trim() : '';
|
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 || '野餐包';
|
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 default_url = (() => {
|
||||||
const u = new URL('https://www.amazon.com/s');
|
const u = new URL('https://www.amazon.com/s');
|
||||||
u.searchParams.set('k', keyword);
|
u.searchParams.set('k', keyword);
|
||||||
u.searchParams.set('__mk_zh_CN', '亚马逊网站');
|
u.searchParams.set('__mk_zh_CN', '亚马逊网站');
|
||||||
u.searchParams.set('crid', 'ZKNCI4U8BBAP');
|
u.searchParams.set('crid', 'ZKNCI4U8BBAP');
|
||||||
u.searchParams.set('sprefix', '野餐bao,caps,388');
|
|
||||||
u.searchParams.set('ref', 'nb_sb_noss');
|
u.searchParams.set('ref', 'nb_sb_noss');
|
||||||
|
if (sort_s) u.searchParams.set('s', sort_s);
|
||||||
|
else u.searchParams.delete('s');
|
||||||
return u.toString();
|
return u.toString();
|
||||||
})();
|
})();
|
||||||
|
|
||||||
const url = (data && data.url) ? String(data.url).trim() : default_url;
|
const url = default_url;
|
||||||
|
|
||||||
let times = 0;
|
let times = 0;
|
||||||
|
|
||||||
@@ -39,17 +56,67 @@ export function amazon_search_list(data, sendResponse) {
|
|||||||
try {
|
try {
|
||||||
const tab = await tab_task.open_async();
|
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 wait_tab_complete = (tab_id) => new Promise((resolve_wait, reject_wait) => {
|
||||||
const injected_result = Array.isArray(injected_result_list) ? injected_result_list[0] : null;
|
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 = {
|
const result = {
|
||||||
code: 0,
|
code: 0,
|
||||||
status: true,
|
status: true,
|
||||||
message: 'ok',
|
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);
|
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(同时会关闭 popup window)
|
||||||
tab.remove(0);
|
tab.remove(0);
|
||||||
@@ -69,16 +136,21 @@ export function amazon_search_list(data, sendResponse) {
|
|||||||
|
|
||||||
amazon_search_list.desc = 'Amazon 搜索结果列表抓取(DOM 解析)';
|
amazon_search_list.desc = 'Amazon 搜索结果列表抓取(DOM 解析)';
|
||||||
amazon_search_list.params = {
|
amazon_search_list.params = {
|
||||||
url: {
|
|
||||||
type: 'string',
|
|
||||||
desc: '可选,传入完整搜索 URL(不传则按 category_keyword 生成)',
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
category_keyword: {
|
category_keyword: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
desc: '分类关键词',
|
desc: '分类关键词',
|
||||||
default: '野餐包',
|
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) {
|
export function injected_amazon_search_list(params) {
|
||||||
const start_url = params && params.url ? String(params.url) : location.href;
|
const start_url = params && params.url ? String(params.url) : location.href;
|
||||||
const category_keyword = params && params.category_keyword ? String(params.category_keyword).trim() : '';
|
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) {
|
function pick_number(text) {
|
||||||
if (!text) return null;
|
if (!text) return null;
|
||||||
@@ -93,14 +94,28 @@ export function injected_amazon_search_list(params) {
|
|||||||
return items;
|
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 items = extract_results();
|
||||||
|
const next_url = pick_next_url();
|
||||||
|
|
||||||
// 只返回一次结果,避免 send 太多影响判定
|
// 只返回一次结果,避免 send 太多影响判定
|
||||||
return {
|
return {
|
||||||
start_url,
|
start_url,
|
||||||
href: location.href,
|
href: location.href,
|
||||||
category_keyword,
|
category_keyword,
|
||||||
|
sort_by,
|
||||||
total: items.length,
|
total: items.length,
|
||||||
items,
|
items,
|
||||||
|
next_url,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,7 @@
|
|||||||
</select>
|
</select>
|
||||||
|
|
||||||
<label class="label">参数(JSON)</label>
|
<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>
|
<textarea id="action_params" class="textarea" spellcheck="false">{}</textarea>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
const action_name_el = document.getElementById('action_name');
|
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 action_params_el = document.getElementById('action_params');
|
||||||
const btn_run_el = document.getElementById('btn_run');
|
const btn_run_el = document.getElementById('btn_run');
|
||||||
const btn_clear_el = document.getElementById('btn_clear');
|
const btn_clear_el = document.getElementById('btn_clear');
|
||||||
@@ -111,6 +112,20 @@ function refresh_params_editor() {
|
|||||||
if (!meta) return;
|
if (!meta) return;
|
||||||
const defaults = build_default_params(meta.params);
|
const defaults = build_default_params(meta.params);
|
||||||
action_params_el.value = JSON.stringify(defaults, null, 2);
|
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() {
|
function refresh_action_select() {
|
||||||
|
|||||||
Reference in New Issue
Block a user