Excel VBA批量抓取亚马逊商品主图:从原理到完整代码
做电商运营的朋友应该都体会过这种痛苦要给竞品做价格监控、图片归档链接打开几十个标签页一张张右键另存为再手动改成商品名。图片多的时候一上午就没了。后来我花了一晚上写了段VBA脚本用Excel直接批量抓取亚马逊商品主图把链接列表粘贴进去点一个按钮图片按ASIN自动命名落到文件夹里。这篇就把整个实现过程、完整代码和踩坑记录放出来适合运营、选品、做资料整理的人直接抄作业。1. 这个需求为什么值得动手做1.1 从一次“复制粘贴到手软”说起我最初做这个需求是因为要给一批竞品Listing做视觉分析。当时手上有60多个商品链接需要把主图全部存下来放到同一张表格里做比对。手动操作怎么做打开页面等图片加载右键图片另存为选个文件夹输入乱糟糟的文件名。第一个商品大概花30秒到第五十个的时候手都酸了而且中间漏了两张图回头还得重新找是哪两个链接漏的。这种重复性劳动看着没啥技术含量但时间成本是实打实的。后来我想明白了主图链接本身就在商品页的HTML源码里浏览器能做“查看图片”这个动作说到底就是发送一个HTTP请求拿到HTML然后从里面找到图片地址。既然这样把请求这一步交给Excel解析HTML这步也交给Excel最后把图片二进制数据下载下来存成本地文件完全可行。于是就有了这套方案纯Excel环境内完成“读链接 → 抓页面 → 提取主图URL → 下载图片 → 按商品名/ASIN重命名”的完整流程。没有额外安装Python不用写爬虫框架日常装Office的电脑就能跑。1.2 技术选型VBA与Power Query怎么选先说一下市面上几条常见路线我踩过之后给它们排了个序。Power Query从Web导入Excel 2016及以上内置无代码、可视化适合抓那种“表格型数据”比如维基百科的表格、某个统计页面的数据列表。但它对动态渲染的页面支持很差。亚马逊的商品页不是纯静态表格主图URL藏得比较深Power Query导进来通常是整页HTML文本那你还得做HTML清洗反而更麻烦。所以这个方案被我排除了。浏览器插件如Web Scraper、各类网页抓取插件适合程序员以外的用户点点鼠标就能把结构化数据抓下来。但插件抓完之后数据落到CSV图片文件本身还是在线上还得再处理一步“下载图片”。而且插件受浏览器版本制约淘宝等平台对自动化操作有风控插件触发的请求很容易被识别。这个方案能用但不优雅。VBA XMLHTTP这是我最推荐的方案。原因很简单整个流程闭环都在Excel里从链接列表到图片落盘一步完成VBA的XMLHTTP对象可以自定义User-Agent、Cookie、超时时间能在一定程度模拟真实浏览器访问比插件稳定后续要扩展抓评论数、抓价格、抓评分就是改几行解析代码的事。一句话总结如果只是偶尔抓一两张图手动操作就行如果是批量、周期性的图片归档需求直接用VBA一次搞定长期收益最高。1.3 前置认知网页抓取的基本原理很多人一听“网页抓取”就紧张觉得是要写很长很复杂的东西。其实核心就三步。第一步向服务器发一个GET请求请求的URL就是商品详情页的链接。服务器返回的不是一张图片而是这个页面的HTML源码。你可以把HTML理解成一本书的内容里面有正文、有排版标记、也有插图链接。第二步从HTML里找到“插图链接”。亚马逊商品页的主图一般放在img标签里src属性就是图片URL。我们要做的就是把这个src属性后面的地址抠出来。第三步再向图片URL发一次请求。这次服务器返回的是图片的二进制数据比如JPEG文件的内容把这段二进制写入本地文件图片就下载成功了。浏览器做的也就是这件事请求页面、解析HTML、再请求图片资源、渲染显示。区别在于浏览器自动完成了解析和渲染而我们用代码手动控制每一步。理解了这层原理后面所有步骤都是顺着这个逻辑走的。2. 动手前的准备工作2.1 环境与文件清单先说环境。Excel 2013及以上版本都行我测试过微软Office 2016和Microsoft 365都正常运行。WPS用户要特别注意WPS对VBA支持是分版本的部分版本需要单独安装VBA插件建议先用官方Excel测试或者提前确认你的WPS有VBA模块。用VBA之前必须开启“开发工具”选项卡。操作路径Excel选项 → 自定义功能区 → 勾选“开发工具”。如果这一步没做后面根本找不到Visual Basic入口。宏安全性也要设置一下。建议在开发期间把“宏设置”改成“启用所有宏”验证脚本没问题后再改回“禁用所有宏仅通知”。这一步不是闲的——我发现很多新手写完代码一运行就报错结果发现是宏被禁了。文件保存格式用.xlsm启用宏的工作簿别存成.xlsx不然代码会丢。另外Excel有个老毛病网页抓取请求发出后界面会进入“无响应”状态这是VBA单线程限制导致的属于正常现象。抓取过程中Excel会显示“未响应”等请求完成就恢复了。别因为看到“未响应”就强制关闭程序我就因为这个丢过一次未保存的数据。2.2 理解亚马逊商品页的图片结构要提取主图先得知道主图在HTML源码里长什么样。打开微信公众号后台的编辑器不方便演示咱们直接说浏览器操作随便打开一个亚马逊商品页按F12打开开发者工具切到Elements面板用左上角的“选择元素”按钮点击商品主图就能看到对应的HTML代码。亚马逊的主图HTML结构有几个典型特征主图通常在一个img标签里ID常见的是landingImage也有可能是imgTagWrapperId包裹着的子元素。图片URL一般以https://m.media-amazon.com/images/I/开头后面跟一串数字编码。高清图往往藏在>Public Function FetchHtml(url As String) As String On Error GoTo ErrorHandler Dim http As Object Set http CreateObject(WinHttp.WinHttpRequest.5.1) http.Open GET, url, False http.SetTimeouts 60000, 60000, 60000, 60000 http.setRequestHeader User-Agent, Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 http.setRequestHeader Accept-Language, en-US,en;q0.9 http.Send FetchHtml http.ResponseText Set http Nothing Exit Function ErrorHandler: FetchHtml Set http Nothing End Function几点经验WinHttp.WinHttpRequest.5.1比Microsoft.XMLHTTP更可靠网络超时控制更完善。前者的SetTimeouts方法可以分别设置连接、发送、接收的超时时间避免某个页面卡住20分钟。请求加Accept-Language: en-US是一个很实用的小技巧某些站点会根据请求语言返回不同版本的页面。代码里用False表示同步请求也就是等全部响应完成后再继续执行。VBA里异步请求比较麻烦而且容易造成界面假死所以同步在这个场景下反而是稳定的选择。3.2 从HTML里提取主图URL的两种写法拿到HTML源码后就要在里面找图片地址。最常见的方法是字符串查找加上MID截取。为什么不用正则因为HTML本身不是特别规范属性顺序可能变化而且图片地址里也有各种转义正则写起来很容易出错。用InStr定位关键点再往后截取反而更直观、不易出错。我写了一个通用函数按优先级查找三个关键标记Public Function ExtractMainImage(html As String) As String Dim key As String Dim startPos As Long Dim endPos As Long Dim url As String 优先找高清图标记 key data-old-hires startPos InStr(1, html, key, vbTextCompare) If startPos 0 Then startPos startPos Len(key) If Mid(html, startPos, 1) Then startPos startPos 1 endPos InStr(startPos, html, ) url Mid(html, startPos, endPos - startPos) If Left(url, 4) http Then ExtractMainImage url Exit Function End If End If 其次找landingImage的src key idlandingImage startPos InStr(1, html, key, vbTextCompare) If startPos 0 Then key2 src key2 src startPos InStr(startPos, html, key2, vbTextCompare) startPos startPos Len(key2) endPos InStr(startPos, html, ) url Mid(html, startPos, endPos - startPos) If Left(url, 4) http Then ExtractMainImage url Exit Function End If End If 最后直接按图片域名前缀找 key https://m.media-amazon.com/images/I/ startPos InStr(1, html, key, vbTextCompare) If startPos 0 Then endPos InStr(startPos 1, html, ) ExtractMainImage Mid(html, startPos, endPos - startPos) Exit Function End If ExtractMainImage End Function这个函数的逻辑很简单用InStr在每个关键标记出现的位置开始查找然后用Mid截取从该位置到下一个引号之间的内容。三次查找逐渐放宽条件保证遇到页面结构变化也有兜底方案。注意一个细节InStr(1, html, key, vbTextCompare)里的vbTextCompare是忽略大小写。HTML属性名大小写不固定这个参数至少能覆盖一部分变体。3.3 下载图片并重命名拿到主图URL之后剩下的就是下载。图片是二进制文件不能用当字符串处理的ResponseText得换一个思路用另一套对象来接收字节流然后以二进制方式写入磁盘。我用的方案是Microsoft.XMLHTTP配合ADODB.Stream。前者的ResponseBody属性返回的是字节数组后者负责把这个字节数组写入本地文件。Public Function DownloadImage(imgUrl As String, savePath As String) As Boolean On Error GoTo ErrorHandler Dim http As Object Set http CreateObject(Microsoft.XMLHTTP) http.Open GET, imgUrl, False http.Send If http.Status 200 Then DownloadImage False Exit Function End If Dim stream As Object Set stream CreateObject(ADODB.Stream) stream.Type 1 stream.Open stream.Write http.ResponseBody stream.SaveToFile savePath, 2 stream.Close DownloadImage True Exit Function ErrorHandler: DownloadImage False End Function写文件这里要注意SaveToFile的第二个参数2表示覆盖写入如果本地已经有同名文件就直接覆盖。如果你希望跳过已下载的图片改成1就不会覆盖了然后自己写一段逻辑判断文件是否已存在。图片下载的文件名我建议直接用商品名称或者ASIN。ASIN的好处是全球唯一不会因为两个商品名称相同而互相覆盖商品名称的好处是肉眼看得懂。我通常的做法是“ASIN_商品名称.jpg”两全其美。命名格式可以在表格里预先拼接好VBA只负责读表格里的“自定义文件名”列更灵活。3.4 进度显示与错误日志批量下载几十张图片跑一次也就几分钟。但中间只要有几个链接报错你就得知道是谁报错了、错在哪。所以我的脚本里有三件套状态栏文字提示、单元格状态标记、最后弹窗汇总。状态栏文字提示在循环里不断更新Application.StatusBar 正在下载第 5/50 张图片...跑的时候能随时看到进度不用死等。跑完记得把状态栏恢复成Application.StatusBar False不然状态栏会一直留着“正在下载”的字样。单元格状态标记在链接列表右边加一列“状态”每处理完一个商品就写“成功”或“失败原因”。这样跑完之后你只要看这列颜色就知道哪些要重跑不用翻系统日志。最后弹窗汇总用一个字符串变量不断累加错误信息最后用MsgBox汇总显示比如“成功下载45张失败5张失败链接查看第3到7行”。这比跑完之后糊里糊涂地重新来一遍要强得多。下面是主循环的骨架代码Sub BatchDownloadMainImages() Dim ws As Worksheet Set ws ThisWorkbook.Sheets(Sheet1) Dim lastRow As Long lastRow ws.Cells(ws.Rows.Count, 1).End(xlUp).Row Dim saveDir As String saveDir ThisWorkbook.Path \images\ On Error Resume Next MkDir saveDir On Error GoTo 0 Dim i As Long Dim nameVal As String Dim urlVal As String Dim html As String Dim imgUrl As String Dim saveName As String For i 2 To lastRow nameVal Trim(ws.Cells(i, 1).Value) urlVal Trim(ws.Cells(i, 2).Value) If urlVal Then Exit For Application.StatusBar 正在处理第 i - 1 / lastRow - 1 个商品 DoEvents html FetchHtml(urlVal) If html Then ws.Cells(i, 3).Value 失败页面请求失败 GoTo NextRow End If imgUrl ExtractMainImage(html) If imgUrl Then ws.Cells(i, 3).Value 失败未找到主图URL GoTo NextRow End If saveName SanitizeFileName(nameVal) If DownloadImage(imgUrl, saveDir saveName .jpg) Then ws.Cells(i, 3).Value 成功 imgUrl Else ws.Cells(i, 3).Value 失败图片下载失败 End If NextRow: DoEvents Next i Application.StatusBar False MsgBox 批量抓取完成 End Sub这里还要补充一个文件名校验函数把Windows不认的字符替换掉Public Function SanitizeFileName(inputName As String) As String Dim illegalChars As Variant illegalChars Array(\, /, :, *, ?, , , , |) Dim i As Integer For i LBound(illegalChars) To UBound(illegalChars) inputName Replace(inputName, illegalChars(i), _) Next i SanitizeFileName inputName End Function文件名我一般还会做长度限制超过40个字符就截断防止有些页面标题太长导致保存失败。操作上就是在返回之前加一句If Len(SanitizeFileName) 40 Then SanitizeFileName Left(SanitizeFileName, 40)。4. 常见问题与排查技巧实录4.1 图片尺寸后缀与高清图处理下载下来的图片如果发现很模糊大概率是抓到的是缩略图而不是高清原图。亚马逊的图片URL里带尺寸参数比如._AC_SX150_SY150_QL70_.jpg这种SX150就表示宽度150像素。解决方案有两种。第一种是替换尺寸参数。观察链接规律后直接用Replace把它处理成._SL1500_.jpg这样能统一拉到大尺寸版本。但这个方法对结构特别复杂的URL不一定完全可靠因为不同图片可能有不同数量的参数。第二种更稳妥重新检查HTML里有没有sides索引图区。很多商品页的主图区域有一组缩略图每张缩略图都对应一个大图URL。这个URL通常会带上_AC_UL320_这类标签但也常有个字段能拼出原始大图。实际开发中我建议优先用>