首页
留言
Search
1
在Centos7下搭建Socks5代理服务器
1,279 阅读
2
在windows11通过Zip安装Mysql5.7
872 阅读
3
Mysql5.7开放远程登录
782 阅读
4
数据库
739 阅读
5
mysql5.7基本命令
646 阅读
综合
正则表达式
git
系统
centos7
ubuntu
kali
Debian
网络
socks5
wireguard
运维
docker
hadoop
kubernetes
hive
openstack
ElasticSearch
ansible
前端
三剑客
油猴脚本
Python
Python3
selenium
Flask
PHP
PHP基础
ThinkPHP
游戏
我的世界
算法
递归
排序
查找
软件
ide
Xshell
vim
PicGo
Typora
云盘
安全
靶场
reverse
Java
JavaSE
Spring
MyBatis
C++
QT
数据库
mysql
登录
Search
标签搜索
java
centos7
linux
centos
JavaScript
html5
php
css3
mysql
spring
mysql5.7
linux全栈
ubuntu
BeanFactory
SpringBean
python
python3
ApplicationContext
kali
mysql8.0
我亏一点
累计撰写
141
篇文章
累计收到
1
条评论
首页
栏目
综合
正则表达式
git
系统
centos7
ubuntu
kali
Debian
网络
socks5
wireguard
运维
docker
hadoop
kubernetes
hive
openstack
ElasticSearch
ansible
前端
三剑客
油猴脚本
Python
Python3
selenium
Flask
PHP
PHP基础
ThinkPHP
游戏
我的世界
算法
递归
排序
查找
软件
ide
Xshell
vim
PicGo
Typora
云盘
安全
靶场
reverse
Java
JavaSE
Spring
MyBatis
C++
QT
数据库
mysql
页面
留言
搜索到
1
篇与
油猴脚本
的结果
2026-03-14
油猴 1688列表自动爬取数据
油猴 1688列表自动爬取数据此代码为油猴脚本,用于1688商品搜索列表和店铺商品列表爬取自动数据,请在商品两处地方补充自己的代码,将商品信息上传到自己的数据库。代码原理是,hook 1688的xhr请求,请求中途拦截响应,取出请求内容处理。但是有些请求是通过JSONP的方式,就是通过script标签动态插入body,从而让浏览器加载,所以同样监听了script加载内容回调,符合的响应内容则返回。对于商品详情的数据,则通过iframe,父页面开启监听,子页面进行推送。// ==UserScript== // @name 1688列表自动爬取数据 // @namespace http://tampermonkey.net/ // @version 0.2 // @description 1688列表自动爬取数据 // @author 我亏一点 // @match *.1688.com/page/offerlist.htm* // @match *.1688.com/offer/*.html* // @match *.1688.com/selloffer/offer_search.htm* // @icon https://s1.ax1x.com/2022/10/14/xwsJYT.png // @grant none // ==/UserScript== (function () { "use strict"; const config = { lock: false, //是否自动爬取 true为是,false为否 run_status: false, //是否正在拦截爬取 sleep: 5, // 爬取间隔 (秒) iframe_timeout: 3600, // 最大等待时间(秒),防止卡死 debug: false, // 是否开启测试,开启只会爬一个数据 data: [], //数据集 is_iframe: window.self !== window.top, type: "", // 页面类型 shop_list为店铺商品列表,goods_list为搜索商品列表,detail为商品详情 type_match: { shop_list: "mtop.alibaba.alisite.cbu.server.moduleasyncservice", // 店铺商品列表 goods_list: "mtop.relationrecommend.wirelessrecommend.recommend", // 搜索商品列表 }, }; //================创建面板开始==================== // 创建一个 div 元素用于显示日志 const logDiv = document.createElement("div"); logDiv.id = "log-div-1688"; logDiv.style.position = "fixed"; logDiv.style.bottom = "10px"; logDiv.style.left = "10px"; logDiv.style.width = "400px"; logDiv.style.height = "300px"; logDiv.style.overflowY = "auto"; logDiv.style.border = "10px solid black"; logDiv.style.backgroundColor = "white"; logDiv.style.zIndex = 9999; logDiv.style.padding = "20px"; logDiv.style.boxShadow = "0 4px 8px rgba(0, 0, 0, 0.1)"; logDiv.style.wordWrap = "break-word"; logDiv.style.overflowWrap = "break-word"; logDiv.style.fontFamily = "monospace"; logDiv.style.fontSize = "12px"; // 日志容器 - 使用单独的容器存放日志条目 const logContainer = document.createElement("div"); logContainer.id = "log-container-1688"; logContainer.style.maxHeight = "240px"; logContainer.style.overflowY = "auto"; logDiv.appendChild(logContainer); // 日志头部 const logHeader = document.createElement("p"); logHeader.style.color = "green"; logHeader.style.fontWeight = "bold"; logHeader.style.margin = "0 0 10px 0"; logHeader.textContent = "插件初始化——我亏一点"; logDiv.insertBefore(logHeader, logContainer); document.body.appendChild(logDiv); // 日志配置 const logConfig = { maxLines: 200, // 最大日志行数 batchUpdate: true, // 批量更新 autoScroll: true, // 自动滚动 }; // 日志缓冲队列 let logBuffer = []; let logUpdateTimer = null; let pendingScroll = false; // 标记是否需要滚动 // 优化后的日志添加函数 function addLog(html_code, lock = true) { logBuffer.push(html_code); // 标记需要滚动 if (lock && logConfig.autoScroll) { pendingScroll = true; } // 批量更新,避免频繁 DOM 操作 if (!logUpdateTimer) { logUpdateTimer = setTimeout(flushLogBuffer, 50); } } // 刷新日志缓冲 function flushLogBuffer() { if (logBuffer.length === 0) { logUpdateTimer = null; return; } // 使用 DocumentFragment 批量插入 const fragment = document.createDocumentFragment(); logBuffer.forEach((html) => { const p = document.createElement("p"); p.innerHTML = html; p.style.margin = "2px 0"; fragment.appendChild(p); }); logContainer.appendChild(fragment); // 限制日志行数,自动清理旧日志 while (logContainer.children.length > logConfig.maxLines) { logContainer.removeChild(logContainer.firstChild); } logBuffer = []; logUpdateTimer = null; // DOM 更新后执行滚动 if (pendingScroll) { pendingScroll = false; scrollToBottom(); } } // 滚动到底部函数 function scrollToBottom() { requestAnimationFrame(() => { setTimeout(() => { logContainer.scrollTop = logContainer.scrollHeight; }, 10); }); } // 清空日志功能 function clearLog() { logContainer.innerHTML = ""; logBuffer = []; pendingScroll = false; } // 添加清空按钮到日志头部 const clearBtn = document.createElement("button"); clearBtn.textContent = "清空日志"; clearBtn.style.marginLeft = "10px"; clearBtn.style.fontSize = "10px"; clearBtn.style.cursor = "pointer"; clearBtn.onclick = clearLog; logHeader.appendChild(clearBtn); // 拖动相关变量 let isDragging = false; let currentX; let currentY; let initialX; let initialY; let xOffset = 0; let yOffset = 0; logDiv.addEventListener("mousedown", dragStart); document.addEventListener("mousemove", drag); document.addEventListener("mouseup", dragEnd); function dragStart(e) { if (e.target.tagName === "BUTTON" || e.target.id === "start_run_button") { return; } initialX = e.clientX - xOffset; initialY = e.clientY - yOffset; if (e.target === logDiv) { isDragging = true; logDiv.style.cursor = "grabbing"; } } function drag(e) { if (isDragging) { e.preventDefault(); currentX = e.clientX - initialX; currentY = e.clientY - initialY; xOffset = currentX; yOffset = currentY; setTranslate(currentX, currentY, logDiv); } } function dragEnd(e) { initialX = currentX; initialY = currentY; isDragging = false; logDiv.style.cursor = "grab"; } function setTranslate(xPos, yPos, el) { el.style.transform = `translate3d(${xPos}px, ${yPos}px, 0)`; } //================创建面板结束==================== //================页面检测开始================== // 如果跳转登录,提示请登录 if (window.location.href.includes("login.taobao.com")) { addLog("<p style='color: red'>请自行登录账号</p>"); return; } // 如果在商品详情页面 if (window.location.href.includes("detail.1688.com/offer")) { config.type = "detail"; addLog("<p style='color:green'>已进入商品详情页面</p>"); const item_detail = window.context.result.global.globalData.model; addLog( `<p style='color:green'>商品编号:${item_detail.offerDetail.offerId}</p>`, ); addLog( `<p style='color:green'>商品名称:${item_detail.offerDetail.subject}</p>`, ); addLog( `<p style='color:green'>商品价格:${item_detail.tradeModel.priceDisplay}</p>`, ); addLog( `<p style='color:green'>商品首图:${item_detail.offerDetail.mainImageList[0].fullPathImageURI}</p>`, ); if (config.is_iframe) { addLog(`<p style='color:green'>检测到子页面,自动推送数据</p>`); window.parent.postMessage( { type: "item_detail", data: item_detail, }, "*", ); } return; } // 如果在店铺商品列表或者搜索商品列表页面 else if ( window.location.href.includes("1688.com/page/offerlist.htm") || window.location.href.includes("1688.com/selloffer/offer_search.htm") ) { // 如果是店铺商品列表页面 if (window.location.href.includes("1688.com/page/offerlist.htm")) { config.type = "shop_list"; addLog("<p style='color:green'>已进入店铺商品列表页面</p>"); } // 如果是搜索商品列表页面 if (window.location.href.includes("1688.com/selloffer/offer_search.htm")) { config.type = "goods_list"; addLog("<p style='color:green'>已进入搜索商品列表页面</p>"); } // 如果不在子页面,则监听消息 if (!config.is_iframe) { addLog("<p style='color:green'>自动监听消息</p>"); window.addEventListener("message", function (e) { if (e.data.type === "item_detail") { const item_detail = e.data.data; addLog( `<p style='color:green'>商品编号:${item_detail.offerDetail.offerId}</p>`, ); addLog( `<p style='color:green'>商品名称:${item_detail.offerDetail.subject}</p>`, ); addLog( `<p style='color:green'>商品价格:${item_detail.tradeModel.priceDisplay}</p>`, ); addLog( `<p style='color:green'>商品首图:${item_detail.offerDetail.mainImageList[0].fullPathImageURI}</p>`, ); if (typeof currentItemResolver === "function") { currentItemResolver(item_detail); } // 可以在这里写把数据保存到数据库的逻辑 } }); } // 如果配置锁开启,则自动开始爬取 if (config.lock) { addLog( "<p style='color: blue'>自动爬取:" + (config.lock ? "启动" : "关闭") + "</p>", ); window.onload = () => { run(); }; } else { // 否则添加开始按钮 const startBtn = document.createElement("button"); startBtn.textContent = "开始爬取"; startBtn.style.marginLeft = "10px"; startBtn.style.fontSize = "10px"; startBtn.style.cursor = "pointer"; startBtn.onclick = run; logHeader.appendChild(startBtn); } } // 如果不是商品列表页面,则提示请进入商品列表页面 else { addLog("<p style='color: red'>请进入商品列表页面</p>"); return; } //================页面检测结束================== //================拦截请求开始================== // hook请求方法 const originalOpen = XMLHttpRequest.prototype.open; const originalSend = XMLHttpRequest.prototype.send; XMLHttpRequest.prototype.open = function () { originalOpen.apply(this, arguments); this.addEventListener("load", function () { switch (config.type) { case "shop_list": if ( this.responseURL .toLocaleLowerCase() .includes(config.type_match.shop_list.toLocaleLowerCase()) && config.run_status ) { try { const list = JSON.parse(this.responseText).data.content.offerList; if (list) { const pageNum = document.querySelector( 'div[style*="background: rgb(255, 64, 0)"]', )?.textContent || "未知"; addLog("<p>开始爬取第 " + pageNum + " 页</p>"); processAllItemsSequentially(list).then(() => { setTimeout(() => { if ( document.querySelectorAll("button")[1] && !config.debug ) { document.querySelectorAll("button")[1].click(); } }, config.sleep * 1000); }); } } catch (e) { addLog("<p style='color: red'>列表数据解析失败</p>"); } } break; default: break; } }); }; XMLHttpRequest.prototype.send = function () { originalSend.apply(this, arguments); }; //================拦截请求结束================== //================拦截jsonp请求开始================== // 保存原生 src 的 setter const srcDescriptor = Object.getOwnPropertyDescriptor( HTMLScriptElement.prototype, "src", ); const originalSrcSetter = srcDescriptor.set; // 重写 src setter,拦截所有 script.src 赋值 Object.defineProperty(HTMLScriptElement.prototype, "src", { // 重写 setter set(url) { // 如果不是 1688/淘宝的 mtop JSONP,直接走原生逻辑 if (typeof url !== "string" || !isMtopJsonpUrl(url)) { return originalSrcSetter.call(this, url); } // 从 URL 中取出 callback 参数 const callbackName = extractCallbackName(url); if (!callbackName) { return originalSrcSetter.call(this, url); } // 在真正设置 src 之前,先 hook 这个全局回调函数 hookMtopJsonpCallback(callbackName, url); // 再让浏览器正常去加载这个 script return originalSrcSetter.call(this, url); }, get() { return srcDescriptor.get.call(this); }, configurable: true, }); // 判断是否是 1688/淘宝 mtop JSONP 请求 function isMtopJsonpUrl(url) { if (!url) return false; const lower = url.toLowerCase(); return ( (lower.includes("h5api.m.1688.com/h5/mtop.") || lower.includes("h5api.m.taobao.com/h5/mtop.")) && /[?&]callback=mtopjsonp/i.test(url) ); } // 从 URL 中解析 callback 参数名 function extractCallbackName(url) { try { const search = url.split("?")[1]; if (!search) return null; const params = new URLSearchParams(search); const callback = params.get("callback"); return callback || null; } catch (e) { return null; } } /** * 动态替换全局 JSONP 回调函数 * @param {string} callbackName - 如 mtopjsonp5 * @param {string} url - 原始 JSONP URL(用于日志、匹配接口) */ function hookMtopJsonpCallback(callbackName, url) { const win = window; // 如果回调已经是函数,则直接劫持覆盖 if (typeof win[callbackName] === "function") { // 如果已经被 hook 过,就不重复,防止同页面请求爬取多次 if (win["__" + callbackName + "__hooked__"]) return; win["__" + callbackName + "__hooked__"] = true; const raw = win[callbackName]; win[callbackName] = function (data) { handleMtopJsonResponse(url, data); return raw.apply(this, arguments); }; return; } // 如果没有回调函数,则劫持函数赋值,等待赋值再进行劫持 Object.defineProperty(win, callbackName, { configurable: true, // 允许后续重新定义 enumerable: true, get: function () { // 返回 undefined 或者你缓存的函数,通常不影响逻辑 return win["__proxy_" + callbackName]; }, set: function (realCallback) { // 此时页面正在把真正的回调函数赋值给这个变量 // 我们把这个属性"篡改"成我们的函数,并保存真正的回调 Object.defineProperty(win, callbackName, { value: function (data) { // console.log("[JSONP HOOK] 拿到数据:", data); // 拦截数据处理 handleMtopJsonResponse(url, data); // 继续运行原函数 if (typeof realCallback === "function") { return realCallback.apply(this, arguments); } }, writable: true, // 允许被删除或修改 configurable: true, // 允许被删除 enumerable: true, }); }, }); } /** * 根据接口类型分发处理 * @param {string} url * @param {any} data */ function handleMtopJsonResponse(url, data) { const lowerUrl = url.toLowerCase(); if ( lowerUrl.includes("mtop.relationrecommend.wirelessrecommend.recommend") ) { handleGoodsListJsonp(data); } } /** * 处理搜索列表 goods_list 的 JSONP 数据 * @param {any} data */ function handleGoodsListJsonp(data) { if (!config.run_status) return; try { const list = data && data.data && data.data.data && data.data.data.OFFER && data.data.data.OFFER.items; if (!list) { addLog("<p style='color: red'>JSONP 列表数据格式异常</p>"); return; } const pageNum = document.querySelector(".fui-current")?.textContent || "未知"; addLog("<p>开始爬取第 " + pageNum + " 页(JSONP)</p>"); processAllItemsSequentially(list).then(() => { setTimeout(() => { if (document.querySelector(".fui-next") && !config.debug) { document.querySelector(".fui-next").click(); } }, config.sleep * 1000); }); } catch (e) { console.error(e); addLog("<p style='color: red'>JSONP 列表数据解析失败</p>"); } } //================拦截jsonp请求结束================== //=================处理商品详情开始================== // 用于在拦截器和处理函数之间通信 let currentItemResolver = null; function run() { if (config.run_status) { addLog("<p style='color: blue'>已开始自动爬取</p>"); return; } config.run_status = true; addLog("<p style='color: blue'>开始自动爬取</p>"); let firstPageBtn = null; if (config.type === "goods_list") { firstPageBtn = document.querySelector(".fui-current"); } if (config.type === "shop_list") { firstPageBtn = document.querySelector( 'div[style*="background: rgb(255, 64, 0)"]', ); } if (firstPageBtn) { firstPageBtn.click(); } else { addLog("<p style='color: red'>未找到分页按钮,请确保在商品列表页</p>"); } } async function processItemDetail(item) { return new Promise((resolve) => { addLog(`<p style='color: blue'>开始处理商品: ${item.subject}</p>`); const iframe = document.createElement("iframe"); iframe.style.width = "800px"; iframe.style.height = "600px"; iframe.style.border = "1px solid #ccc"; iframe.style.position = "fixed"; iframe.style.top = "0"; iframe.style.right = "0"; iframe.style.zIndex = "9999"; iframe.src = `https://detail.1688.com/offer/${item.id}.html`; // 定义清理和关闭的逻辑 const cleanup = (data = null) => { if (iframe.parentNode) { document.body.removeChild(iframe); addLog( `<p style='color: green'>商品: ${item.subject} 处理完成${data ? " (数据已保存)" : " (超时或失败)"}</p>`, ); } resolve(); }; // 将 resolver 暴露给全局拦截器 currentItemResolver = cleanup; // 设置一个最大超时时间,防止某些商品加载不出数据导致脚本卡死 const timeoutId = setTimeout(() => { if (currentItemResolver === cleanup) { addLog( `<p style='color: red'>商品: ${item.subject} 捕获超时,自动跳过</p>`, ); currentItemResolver = null; cleanup(); } }, config.iframe_timeout * 1000); iframe.onload = function () {}; iframe.onerror = function () { addLog( `<p style='color: red'>商品: ${item.subject} iframe加载失败</p>`, ); clearTimeout(timeoutId); cleanup(); }; document.body.appendChild(iframe); }); } async function processAllItemsSequentially(list) { for (const item of list) { if (config.type == "goods_list") { item.subject = item.data.title; item.id = item.data.offerId; } if (item && item.id) { // 可以对接数据库校验是否已存在 await processItemDetail(item); if (config.debug) { break; } // 休眠 addLog(`<p style='color: blue'>等待${config.sleep}秒</p>`); await new Promise((r) => setTimeout(r, config.sleep * 1000)); } } addLog("<p style='color: green'>本页处理完成</p>"); } //=================处理商品详情结束================== })(); 需补充的两处地方,请搜索 数据库位置1 自动入库 // 如果不在子页面,则监听消息 if (!config.is_iframe) { addLog("<p style='color:green'>自动监听消息</p>"); window.addEventListener("message", function (e) { if (e.data.type === "item_detail") { const item_detail = e.data.data; addLog( `<p style='color:green'>商品编号:${item_detail.offerDetail.offerId}</p>`, ); addLog( `<p style='color:green'>商品名称:${item_detail.offerDetail.subject}</p>`, ); addLog( `<p style='color:green'>商品价格:${item_detail.tradeModel.priceDisplay}</p>`, ); addLog( `<p style='color:green'>商品首图:${item_detail.offerDetail.mainImageList[0].fullPathImageURI}</p>`, ); if (typeof currentItemResolver === "function") { currentItemResolver(item_detail); } // 可以在这里写把数据保存到数据库的逻辑 } }); }位置2 校验数据库是否存在 async function processAllItemsSequentially(list) { for (const item of list) { if (config.type == "goods_list") { item.subject = item.data.title; item.id = item.data.offerId; } if (item && item.id) { // 可以对接数据库校验是否已存在 await processItemDetail(item); if (config.debug) { break; } // 休眠 addLog(`<p style='color: blue'>等待${config.sleep}秒</p>`); await new Promise((r) => setTimeout(r, config.sleep * 1000)); } } addLog("<p style='color: green'>本页处理完成</p>"); }
2026年03月14日
3 阅读
0 评论
0 点赞