京东主图以及评论组图下载
// ==UserScript==
// @name 京东商品提取器 (主图/媒体图单选切换+比例区分)
// @namespace http://tampermonkey.net/
// @version 3.0
// @description 主图/媒体图单选切换,主图1:1比例,媒体图3:4比例,互斥显示不同时展示
// @author You
// @match *://item.jd.com/*.html
// @grant GM_registerMenuCommand
// @require https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js
// ==/UserScript==
(function() {
'use strict';
// ========== 配置区 ==========
const CONFIG = {
waitParseTime: 1500, // 页面加载后自动提取主图的等待时间
panelPosition: 'left', // 弹屏位置 left/right
panelWidth: '460px', // 弹屏宽度
autoRun: true, // 打开页面是否自动提取主图
defaultMode: 'main' // 默认选中模式:main=主图模式,media=媒体图模式
};
// ========== 全局变量 ==========
let globalProductInfo = {}; // 商品基础信息
let globalMainImageList = []; // 主图(原轮播图)列表
let globalMediaImageList = []; // 媒体层图片列表
let currentMode = CONFIG.defaultMode; // 当前选中的模式
let panelDom = null; // 弹屏DOM
// ========== 1. 商品基础信息提取(不变) ==========
function extractProductInfo() {
let sku = null;
let productTitle = "未知商品";
const fullHtml = document.documentElement.innerHTML;
// 优先从chat.jd.com链接提取pid作为SKU
const chatLinkRegex = /<a[^>]+href="([^"]*chat\.jd\.com[^"]*)"[^>]*>/g;
let linkMatch;
while ((linkMatch = chatLinkRegex.exec(fullHtml)) !== null) {
const pidMatch = linkMatch[1].match(/[?&]pid=(\d+)/);
if (pidMatch && pidMatch[1]) {
sku = pidMatch[1];
console.log(`✅ 从客服链接提取到SKU(pid): ${sku}`);
break;
}
}
// 兜底:从URL提取SKU
if (!sku) {
const urlMatch = window.location.pathname.match(/\/(\d+)\.html/);
sku = urlMatch ? urlMatch[1] : "unknown_sku";
console.log(`⚠️ 未找到pid,从URL提取SKU: ${sku}`);
}
// 提取商品名
const titleEl1 = document.querySelector(".sku-title-name");
const titleEl2 = document.querySelector(".sku-name");
if (titleEl1) productTitle = titleEl1.innerText.trim();
else if (titleEl2) productTitle = titleEl2.innerText.trim();
else productTitle = document.title.replace(/【.*?】|京东|,.*/g, "").trim();
// 清洗非法字符,生成规范文件夹名
const safeTitle = productTitle.replace(/[\\/:*?"<>|\r\n]/g, "").replace(/\s+/g, " ");
const folderName = `${sku}----${safeTitle}`;
globalProductInfo = { sku, title: safeTitle, folderName };
return globalProductInfo;
}
// ========== 2. 主图提取(原轮播图逻辑不变) ==========
function extractMainImages() {
const imageUrlSet = new Set();
const carouselContainer = document.querySelector('.image-carousel-content');
if (carouselContainer) {
const imgs = carouselContainer.querySelectorAll('img');
imgs.forEach(img => {
let src = img.src || img.getAttribute('data-lazy-img') || img.getAttribute('src');
if (src) {
if (src.startsWith('//')) src = 'https:' + src;
let cleanUrl = src.replace(/\/s\d+x\d+_/, '/'); // 删尺寸前缀
cleanUrl = cleanUrl.replace('.avif', ''); // 删.avif后缀
imageUrlSet.add(cleanUrl);
}
});
} else {
// 兜底全局提取
const fullHtml = document.documentElement.innerHTML;
const imgRegex = /(https?:)?\/\/img\d+\.360buyimg\.com\/[^"'\s]+\.(avif|jpg|png)/g;
let m;
while((m = imgRegex.exec(fullHtml)) !== null) {
let u = m[0].startsWith('//') ? 'https:' + m[0] : m[0];
u = u.replace(/\/s\d+x\d+_/, '/').replace('.avif', '');
imageUrlSet.add(u);
}
}
globalMainImageList = Array.from(imageUrlSet);
console.log(`✅ 提取到主图共 ${globalMainImageList.length} 张`, globalMainImageList);
return globalMainImageList;
}
// ========== 3. 媒体层图片提取(逻辑不变,仅适配切换) ==========
function extractMediaImages() {
const imageUrlSet = new Set();
const mediaContainer = document.querySelector('.jdc-pc-media-preview-list');
// 核心校验:容器不存在,提示用户先打开对应区域
if (!mediaContainer) {
alert("⚠️ 未找到媒体层容器\n请先在页面中打开/展开【商品详情/媒体预览】区域,让 .jdc-pc-media-preview-list 层加载完成后,再重试");
return [];
}
// 提取容器内所有图片,兼容懒加载
const imgs = mediaContainer.querySelectorAll('img');
imgs.forEach(img => {
let src = img.src
|| img.getAttribute('data-lazy-img')
|| img.getAttribute('data-src')
|| img.getAttribute('origin-src')
|| img.getAttribute('src');
if (src) {
if (src.startsWith('//')) src = 'https:' + src;
let cleanUrl = src.replace(/\/s\d+x\d+_/, '/');
cleanUrl = cleanUrl.replace('.avif', '');
imageUrlSet.add(cleanUrl);
}
});
globalMediaImageList = Array.from(imageUrlSet);
console.log(`✅ 提取到媒体层图片共 ${globalMediaImageList.length} 张`, globalMediaImageList);
// 提取后重新渲染面板,刷新预览
renderInfoPanel();
if (globalMediaImageList.length === 0) {
alert("⚠️ 容器内未提取到有效图片\n请确保图片已加载完成(滚动到对应区域让图片渲染),再重试");
} else {
alert(`✅ 媒体图提取成功!共获取到 ${globalMediaImageList.length} 张`);
}
return globalMediaImageList;
}
// ========== 4. 模式切换核心函数 ==========
function switchMode(targetMode) {
if (targetMode === currentMode) return; // 相同模式不重复渲染
currentMode = targetMode;
renderInfoPanel(); // 切换模式后重新渲染面板
console.log(`✅ 已切换到${currentMode === 'main' ? '主图' : '媒体图'}模式`);
}
// ========== 5. 弹屏渲染(核心修改:单选互斥显示+比例区分) ==========
function renderInfoPanel() {
// 已存在面板先移除,避免重复渲染
if (panelDom) document.body.removeChild(panelDom);
// 创建面板DOM
panelDom = document.createElement('div');
panelDom.id = 'jd-extract-panel';
panelDom.style.cssText = `
position: fixed;
${CONFIG.panelPosition}: 0;
top: 50%;
transform: translateY(-50%);
width: ${CONFIG.panelWidth};
max-height: 90vh;
background: #fff;
box-shadow: 0 0 15px rgba(0,0,0,0.2);
z-index: 99999999;
border-radius: 0 8px 8px 0;
padding: 16px;
box-sizing: border-box;
overflow-y: auto;
font-family: Microsoft YaHei, sans-serif;
font-size: 14px;
color: #333;
`;
const { sku, title, folderName } = globalProductInfo;
const isMainMode = currentMode === 'main';
const currentImageList = isMainMode ? globalMainImageList : globalMediaImageList;
const imageCount = currentImageList.length;
// 面板HTML内容
panelDom.innerHTML = `
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px; padding-bottom: 12px; border-bottom: 1px solid #eee;">
<h3 style="margin: 0; font-size: 16px; font-weight: bold; color: #e4393c;">京东商品提取器</h3>
<button id="panel-close-btn" style="border: none; background: #f5f5f5; border-radius: 4px; padding: 4px 8px; cursor: pointer;">关闭</button>
</div>
<!-- 商品基础信息 -->
<div style="margin-bottom: 12px;">
<p style="margin: 6px 0; line-height: 1.5;"><strong>商品SKU:</strong><span style="color: #e4393c; font-weight: bold;">${sku}</span></p>
<p style="margin: 6px 0; line-height: 1.5;"><strong>商品名称:</strong><span style="word-break: break-all; font-size: 12px;">${title}</span></p>
<p style="margin: 6px 0; line-height: 1.5;"><strong>文件夹名:</strong><span style="word-break: break-all; font-size: 12px; color: #666;">${folderName}</span></p>
<p style="margin: 6px 0; line-height: 1.5;">
<strong>当前模式:</strong>
<span style="color: ${isMainMode ? '#0066cc' : '#ff5722'}; font-weight: bold;">
${isMainMode ? '主图模式' : '媒体图模式'}
</span>
<span style="margin: 0 10px;">|</span>
<strong>图片数量:</strong>
<span style="font-weight: bold;">${imageCount} 张</span>
</p>
</div>
<!-- 【核心新增】模式切换单选器 -->
<div style="display: flex; gap: 2px; margin-bottom: 16px; background: #f5f5f5; border-radius: 6px; padding: 3px;">
<button id="mode-main-btn" style="flex: 1; border: none; border-radius: 4px; padding: 8px 0; cursor: pointer; font-weight: bold; transition: all 0.2s; ${isMainMode ? 'background: #e4393c; color: #fff;' : 'background: transparent; color: #666;'}">
主图模式
</button>
<button id="mode-media-btn" style="flex: 1; border: none; border-radius: 4px; padding: 8px 0; cursor: pointer; font-weight: bold; transition: all 0.2s; ${!isMainMode ? 'background: #ff5722; color: #fff;' : 'background: transparent; color: #666;'}">
媒体图模式
</button>
</div>
<!-- 核心操作按钮:一行3个,根据模式动态切换 -->
<div style="display: flex; gap: 8px; margin-bottom: 16px;">
${isMainMode
// 主图模式按钮:下载主图、复制SKU、重新提取
? `
<button id="main-download-btn" style="flex: 1; border: none; background: #e4393c; color: #fff; border-radius: 4px; padding: 8px 0; cursor: pointer; font-weight: bold;">下载主图</button>
<button id="copy-btn" style="flex: 1; border: none; background: #28a745; color: #fff; border-radius: 4px; padding: 8px 0; cursor: pointer; font-weight: bold;">复制SKU</button>
<button id="refresh-btn" style="flex: 1; border: none; background: #0066cc; color: #fff; border-radius: 4px; padding: 8px 0; cursor: pointer; font-weight: bold;">重新提取</button>
`
// 媒体图模式按钮:提取媒体图、下载媒体图、复制SKU
: `
<button id="media-extract-btn" style="flex: 1; border: none; background: #ff5722; color: #fff; border-radius: 4px; padding: 8px 0; cursor: pointer; font-weight: bold;">提取媒体图</button>
<button id="media-download-btn" style="flex: 1; border: none; background: #f57c00; color: #fff; border-radius: 4px; padding: 8px 0; cursor: pointer; font-weight: bold;" ${imageCount === 0 ? 'disabled' : ''}>下载媒体图</button>
<button id="copy-btn" style="flex: 1; border: none; background: #28a745; color: #fff; border-radius: 4px; padding: 8px 0; cursor: pointer; font-weight: bold;">复制SKU</button>
`
}
</div>
<!-- 【核心修改】唯一预览区:根据模式切换比例,绝不两个都显示 -->
<div style="margin-bottom: 12px;">
<div style="margin-bottom: 8px; font-weight: bold; color: ${isMainMode ? '#0066cc' : '#ff5722'};">
${isMainMode ? '主图预览 (1:1正方形 / 点击查看大图)' : '媒体图预览 (3:4竖版 / 点击查看大图)'}
</div>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px;">
${imageCount === 0
? `<div style="grid-column: span 2; text-align: center; color: #999; padding: 40px 20px;">
${isMainMode ? '暂无主图' : '暂无媒体图<br>请先点击【提取媒体图】按钮'}
</div>`
: currentImageList.map((url, index) => `
<div style="border: 1px solid #eee; border-radius: 4px; overflow: hidden; background: #f9f9f9;">
<a href="${url}" target="_blank" style="display: block; text-decoration: none;">
<!-- 核心比例控制:主图1:1,媒体图3:4 -->
<img
src="${url}"
style="width: 100%; aspect-ratio: ${isMainMode ? '1/1' : '3/4'}; object-fit: cover; display: block; border-bottom: 1px solid #eee;"
alt="${isMainMode ? '主图' : '媒体图'}${index}"
onerror="this.style.display='none'"
>
<div style="padding: 6px; text-align: center; font-size: 12px; color: #666;">
${isMainMode ? `${index}.jpg` : `media_${index}.jpg`}
</div>
</a>
</div>
`).join('')
}
</div>
</div>
`;
// 插入到页面
document.body.appendChild(panelDom);
// 绑定所有按钮事件
bindPanelEvents();
}
// ========== 6. 按钮事件绑定 ==========
function bindPanelEvents() {
// 关闭按钮
document.getElementById('panel-close-btn').addEventListener('click', () => {
if (panelDom) document.body.removeChild(panelDom);
panelDom = null;
});
// 模式切换按钮
document.getElementById('mode-main-btn').addEventListener('click', () => switchMode('main'));
document.getElementById('mode-media-btn').addEventListener('click', () => switchMode('media'));
// 复制SKU按钮(两个模式通用)
document.getElementById('copy-btn').addEventListener('click', async () => {
const { sku, title } = globalProductInfo;
const textToCopy = `${sku}----${title}`;
try {
await navigator.clipboard.writeText(textToCopy);
const copyBtn = document.getElementById('copy-btn');
const originalText = copyBtn.innerText;
copyBtn.innerText = '✅ 已复制';
copyBtn.style.background = '#218838';
setTimeout(() => {
copyBtn.innerText = originalText;
copyBtn.style.background = '#28a745';
}, 1500);
} catch (err) {
console.error('复制失败:', err);
alert('❌ 复制失败,请手动复制');
}
});
// ========== 主图模式按钮事件 ==========
if (currentMode === 'main') {
// 重新提取主图
document.getElementById('refresh-btn').addEventListener('click', () => {
extractMainImages();
renderInfoPanel();
alert('✅ 主图重新提取完成');
});
// 下载主图
document.getElementById('main-download-btn').addEventListener('click', downloadMainImages);
}
// ========== 媒体图模式按钮事件 ==========
if (currentMode === 'media') {
// 提取媒体图
document.getElementById('media-extract-btn').addEventListener('click', extractMediaImages);
// 下载媒体图
document.getElementById('media-download-btn').addEventListener('click', downloadMediaImages);
}
}
// ========== 7. 主图下载逻辑(原有规范不变) ==========
async function downloadMainImages() {
const { sku, title, folderName } = globalProductInfo;
const imageUrls = globalMainImageList;
if (imageUrls.length === 0) { alert("⚠️ 无主图可下载"); return; }
if (!window.showDirectoryPicker) { alert("⚠️ 请使用 Chrome/Edge 86+ 版本浏览器"); return; }
try {
const rootDirHandle = await window.showDirectoryPicker({ mode: 'readwrite', startIn: 'downloads' });
const productDirHandle = await rootDirHandle.getDirectoryHandle(folderName, { create: true });
// 写入skutitle.txt
const textFileHandle = await productDirHandle.getFileHandle('skutitle.txt', { create: true });
const textWritable = await textFileHandle.createWritable();
await textWritable.write(title);
await textWritable.close();
// 批量下载主图
let successCount = 0;
const downloadBtn = document.getElementById('main-download-btn');
const originalText = downloadBtn.innerText;
for (let i = 0; i < imageUrls.length; i++) {
const url = imageUrls[i];
try {
downloadBtn.innerText = `下载中 ${i+1}/${imageUrls.length}`;
downloadBtn.disabled = true;
const response = await axios.get(url, { responseType: 'blob', timeout: 15000 });
let fileExt = 'jpg';
if (url.includes('.png')) fileExt = 'png';
if (url.includes('.webp')) fileExt = 'webp';
const fileName = `${i}.${fileExt}`;
const imgFileHandle = await productDirHandle.getFileHandle(fileName, { create: true });
const imgWritable = await imgFileHandle.createWritable();
await imgWritable.write(response.data);
await imgWritable.close();
successCount++;
} catch (err) {
console.error(`❌ 主图第${i}张下载失败`, err);
}
}
downloadBtn.innerText = originalText;
downloadBtn.disabled = false;
alert(`🎉 主图下载完成!\n成功保存 ${successCount}/${imageUrls.length} 张`);
} catch (err) {
if (err.name !== 'AbortError') {
console.error(err);
alert("❌ 主图下载出错: " + err.message);
const btn = document.getElementById('main-download-btn');
if(btn) { btn.innerText = '下载主图'; btn.disabled = false; }
}
}
}
// ========== 8. 媒体图下载逻辑(原有规范不变) ==========
async function downloadMediaImages() {
const { sku, title, folderName } = globalProductInfo;
const imageUrls = globalMediaImageList;
if (imageUrls.length === 0) {
alert("⚠️ 无媒体图可下载\n请先点击【提取媒体图】按钮,成功提取后再下载");
return;
}
if (!window.showDirectoryPicker) {
alert("⚠️ 请使用 Chrome/Edge 86+ 版本浏览器");
return;
}
try {
const rootDirHandle = await window.showDirectoryPicker({ mode: 'readwrite', startIn: 'downloads' });
const productDirHandle = await rootDirHandle.getDirectoryHandle(folderName, { create: true });
// 补全skutitle.txt
try {
await productDirHandle.getFileHandle('skutitle.txt', { create: false });
} catch {
const textFileHandle = await productDirHandle.getFileHandle('skutitle.txt', { create: true });
const textWritable = await textFileHandle.createWritable();
await textWritable.write(title);
await textWritable.close();
}
// 批量下载媒体图
let successCount = 0;
const downloadBtn = document.getElementById('media-download-btn');
const originalText = downloadBtn.innerText;
for (let i = 0; i < imageUrls.length; i++) {
const url = imageUrls[i];
try {
downloadBtn.innerText = `下载中 ${i+1}/${imageUrls.length}`;
downloadBtn.disabled = true;
const response = await axios.get(url, { responseType: 'blob', timeout: 15000 });
let fileExt = 'jpg';
if (url.includes('.png')) fileExt = 'png';
if (url.includes('.webp')) fileExt = 'webp';
const fileName = `media_${i}.${fileExt}`;
const imgFileHandle = await productDirHandle.getFileHandle(fileName, { create: true });
const imgWritable = await imgFileHandle.createWritable();
await imgWritable.write(response.data);
await imgWritable.close();
successCount++;
} catch (err) {
console.error(`❌ 媒体图第${i}张下载失败`, err);
}
}
downloadBtn.innerText = originalText;
downloadBtn.disabled = false;
alert(`🎉 媒体图下载完成!\n成功保存 ${successCount}/${imageUrls.length} 张`);
} catch (err) {
if (err.name !== 'AbortError') {
console.error(err);
alert("❌ 媒体图下载出错: " + err.message);
const btn = document.getElementById('media-download-btn');
if(btn) { btn.innerText = '下载媒体图'; btn.disabled = false; }
}
}
}
// ========== 9. 主执行函数 ==========
function mainExtract() {
console.log("========== 开始提取京东商品信息 ==========");
extractProductInfo();
extractMainImages();
renderInfoPanel();
console.log("========== 提取完成,已渲染左侧面板 ==========");
}
// ========== 10. 页面加载监听 ==========
window.addEventListener('load', () => {
if (CONFIG.autoRun) {
setTimeout(() => mainExtract(), CONFIG.waitParseTime);
}
});
// ========== 11. 手动执行菜单 ==========
GM_registerMenuCommand("🔍 手动提取商品信息", mainExtract);
GM_registerMenuCommand("📸 手动提取媒体层图片", extractMediaImages);
})();
评论