尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

住宅代理与Python爬虫实战:解析亚马逊感恩节商品数据采集

住宅代理与Python爬虫实战:解析亚马逊感恩节商品数据采集 前几天一个做跨境电商的朋友来找我说自己在亚马逊北美站经营小家电眼瞅着感恩节档期快到了想看看竞品到底在打什么折扣、哪些品类被推到了首页结果自己电脑上打开亚马逊看到的全是本地化推荐跟美国本地用户看到的完全是两个页面根本没法做竞品分析。这事我太熟悉了——做海外电商数据分析第一道坎从来不是分析模型而是怎么稳定地拿到目标地区的真实页面数据。这就要说到我这个项目的主角NetNut。我用它作为网络代理层配合自研爬虫脚本把亚马逊上跟感恩节相关的商品数据批量抓了下来再做价格分布、折扣力度、评分变化和品类热度分析。整套流程跑完之后朋友的选品思路清晰了很多我也把整个项目沉淀成了可以复用的一套方法今天拿出来分享。项目本身不算复杂但里面涉及到网络代理选型、页面结构解析、反爬规避、数据清洗和分析维度设计等一系列环节每一环都有不少坑我会把关键细节和踩坑记录都写出来想复现的朋友可以直接抄作业。1. 项目整体思路与方案选型1.1 为什么选择NetNut住宅代理做亚马逊数据采集最核心的难题就是身份问题。亚马逊对来自不同地区的访问会返回完全不同的页面内容而且它对数据中心IP的识别非常严格一旦检测到机房IP段的大规模请求轻则弹出验证码重则直接封掉整个IP段。这在行业内叫地域化页面和反爬指纹检测两道坎。我之前试过自建代理池买了一堆VPS搭代理结果没跑多久就被亚马逊风控盯上了数据召回率不到一半。后来换NetNut核心原因是它提供的是住宅代理Residential ProxyIP来自真实家庭宽带用户来源看起来就是普通消费者在正常浏览被识别和拦截的概率大幅降低。另外NetNut的会话控制做得比较细可以设置粘性会话Sticky Session让同一个IP在较长时间内保持连接状态这对爬取需要登录态或连续翻页的场景特别友好。说句实话现在做海外电商数据采集用住宅代理基本是行业默认选项了。数据中心代理虽然便宜但面对亚马逊这种级别的风控系统省下的那点成本还不够填封IP的坑。NetNut贵是贵一点但稳定性和数据质量摆在那里对业务型项目来说反而是性价比之选。1.2 技术栈与项目目录结构这次项目我用的是经典的Python三件套requests负责网络请求BeautifulSoup负责页面解析pandas负责数据清洗和分析。这套组合在大规模爬虫项目里不算性能最优但胜在生态成熟、调试方便对这种中小体量的商品数据采集完全够用。项目结构我整理成了这样amazon_thanksgiving_scraper/ ├── config.py # 代理配置和请求参数 ├── scraper.py # 核心爬虫逻辑 ├── parser.py # 页面解析模块 ├── analyzer.py # 数据清洗与分析 ├── requirements.txt ├── data/ │ ├── raw/ # 原始抓取数据 │ └── processed/ # 清洗后的数据 └── output/ └── charts/ # 可视化图表模块划分的考虑是尽量把网络请求、页面解析、数据分析三块解耦。因为实际开发中这三块的调试频率完全不一样——代理配置可能要反复调页面结构经常改分析代码写一次基本不动。放一起虽然看起来省事但改一次就需要全量回归纯属给自己找麻烦。1.3 NetNut代理的参数配置思路NetNut的控制台可以拿到网关地址、端口、用户名和密码配置本质上就是把这些信息拼成一个带认证的代理URL放进requests的proxies参数里。但有几个参数值得专门说一下。粘性会话Sticky Session的时间窗口我设置成了30分钟。太短会导致翻页时IP频繁切换亚马逊会认为这是异常行为太长的话单个IP请求量过大又容易触发频率限制。30分钟刚好覆盖一次完整的列表页抓取流程这是实测下来比较稳的配置。另外一个关键点是会话保持。亚马逊会在一次会话中通过Cookie跟踪用户行为如果每次请求都是全新会话权重会很差。所以我会用requests.Session()来维持Cookie再把代理挂到Session上这样整个抓取过程的人设是连续且一致的被风控系统判为机器人的概率会低很多。2. 亚马逊商品列表页结构与爬取策略2.1 目标页面URL结构拆解亚马逊的搜索列表页URL结构其实很规整可控参数就那几个。比如搜索感恩节相关商品基础URL是base_url https://www.amazon.com/s params { k: thanksgiving deals, # 搜索关键词 page: 1, # 页码 ref: sr_pg_1 # 翻页引用参数 }这里有个小细节ref参数中的页码要和page保持一致。有些爬虫教程会忽略这个参数但亚马逊的判断逻辑里ref的异常会导致搜索结果页返回不完整实测大概有20%的概率返回空列表。我一开始也踩过这个坑补上之后数据召回率立刻上来了。除了thanksgiving deals我还跑了几个关联关键词thanksgiving dinner、black friday deals、holiday gifts、turkey decorations。因为感恩节这个主题下的商品分布很广只跑一个词容易漏掉大量潜在目标多词覆盖才能让后续分析有足够的代表性。2.2 商品卡片HTML结构定位亚马逊的搜索结果每页大约16个商品加上广告位实际可能有20个左右每个商品在一个div[data-component-types-search-result]的容器里。这个选择器是整个解析逻辑的锚点比通过CSS class定位靠谱得多——因为亚马逊的CSS类名经常带乱码后缀换个版本就变而>title_selector h2 span # 商品标题 price_selector span.a-price span.a-offscreen # 当前价格 original_price_selector span.a-text-price span.a-offscreen # 划线原价 rating_selector span.a-icon-alt # 评分 review_selector span.a-size-base.s-underline-text # 评论数注意price_selector和original_price_selector的区别。亚马逊页面里当前价格通常在a-price这个class的容器中而划线原价也就是折扣前的价格在a-text-price里。这个结构对应到人的阅读习惯上就是一个是红色大价格一个是灰色划线价格。解析的时候如果两个值都能拿到就可以算折扣力度如果只拿到一个说明该商品没有明显的促销展示。2.3 请求节奏与反爬规避亚马逊的风控模型其实不复杂但很有效主要就看两个指标单位时间内的请求频率、单IP的请求总量。我的经验是把单位时间的请求间隔控制在2到4秒之间随机化同时每次请求之间加入一些人类操作特征。具体做法是在requests的Headers里加上一组完整的浏览器指纹信息包括User-Agent、Accept-Language、Accept-Encoding这些字段。特别是User-Agent我准备了一个轮换池每翻一页就换一个避免所有请求都挂在同一个UA上。这里推荐一个我自己在用的做法抓取真实Chrome浏览器的请求头然后提取成模板只保留必要的字段比网上那些通用Headers可靠得多。另外一个容易被忽视的点是请求的连接策略。requests默认会在一次TCP连接上复用多个HTTP请求这在普通场景下是优点但在爬虫场景下如果同一个IP的TCP连接数长期处于高位很容易触发服务端的并发检测。所以我给requests加了一个配置每次请求结束都主动断开连接。代价是稍微牺牲一点性能但换来的是风控风险的明显降低这笔账怎么算都划算。3. 核心爬虫代码实现3.1 代理会话封装先把代理配置写好。以NetNut的普通入门版为例配置信息长这样实际账号密码我打码# config.py PROXY_HOST gw-us.nstproxy.com PROXY_PORT 5959 PROXY_USER your_username PROXY_PASS your_password PROXY_URL fhttp://{PROXY_USER}:{PROXY_PASS}{PROXY_HOST}:{PROXY_PORT} REQUEST_HEADERS { 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, Accept-Language: en-US,en;q0.9, Accept: text/html,application/xhtmlxml,application/xml;q0.9,image/avif,image/webp,*/*;q0.8 } KEYWORDS [ thanksgiving deals, thanksgiving dinner, black friday deals, holiday gifts, turkey decorations ] MAX_PAGES 10 REQUEST_DELAY (2, 4) # 随机延迟范围单位秒然后封装代理会话# scraper.py import requests import random import time from config import PROXY_URL, REQUEST_HEADERS, REQUEST_DELAY def create_session(): session requests.Session() session.proxies.update({ http: PROXY_URL, https: PROXY_URL }) session.headers.update(REQUEST_HEADERS) return session def fetch_page(session, url, retries3): 请求页面并支持重试。 重试逻辑遇到网络异常或状态码异常时等待一段时间后重试。 for attempt in range(retries): try: response session.get(url, timeout20) if response.status_code 200: return response.text elif response.status_code 404: return None # 页面不存在不需要重试 else: print(f[{response.status_code}] 请求异常: {url}) except requests.RequestException as e: print(f请求失败({attempt 1}/{retries}): {e}) # 指数退避 随机抖动 wait_time 2 ** attempt random.uniform(0, 1) time.sleep(wait_time) return None这里有几个地方专门说一下。timeout20是必须的因为代理链路比直连链路多一跳响应时间天然会更长没有超时设置的话一个卡死的连接能把整个任务拖住。重试的退避策略我用了指数退避第一次失败等2秒第二次等4秒第三次等8秒这样设计是因为风控触发的封锁往往是临时性的稍等片刻再试大概率能恢复正常。3.2 页面解析与字段提取拿到HTML文本后用BeautifulSoup解析。灵魂代码是这段# parser.py from bs4 import BeautifulSoup import re def parse_search_page(html): soup BeautifulSoup(html, html.parser) items soup.select(div[data-component-types-search-result]) products [] for item in items: product {} # 商品唯一标识 asin item.get(data-asin) if not asin: continue product[asin] asin # 标题 title_tag item.select_one(h2 span) product[title] title_tag.get_text(stripTrue) if title_tag else None # 当前价格 price_tag item.select_one(span.a-price span.a-offscreen) product[price] price_tag.get_text(stripTrue) if price_tag else None # 划线原价 original_price_tag item.select_one(span.a-text-price span.a-offscreen) product[original_price] original_price_tag.get_text(stripTrue) if original_price_tag else None # 评分 rating_tag item.select_one(span.a-icon-alt) product[rating] rating_tag.get_text(stripTrue) if rating_tag else None # 评论数 review_tag item.select_one(span.a-size-base.s-underline-text) product[review_count] review_tag.get_text(stripTrue) if review_tag else None # 商品链接 link_tag item.select_one(h2 a) if link_tag and link_tag.get(href): product[url] https://www.amazon.com link_tag[href].split(?)[0] else: product[url] None products.append(product) return products解析这一块最大的坑是字段值为None。亚马逊的页面并不是每个商品卡片都包含全部字段——有些商品评分数据延迟展示有些促销商品没有划线原价还有些广告位的结构跟普通商品卡片不完全一样。我见过很多新手在解析时直接product[price] price_tag.get_text()一旦price_tag是None整个脚本瞬间崩溃然后就在那排查半天。解决思路是所有字段判断之后再取文本解析不到就留空后续分析的时候再做空值处理。这样既不会中断抓取也让数据天然带上了完整性标记后续做数据质量统计时反而更方便。3.3 多页循环与数据落地核心流程就是遍历每个关键词、遍历每个页码、依次请求和解析然后汇总存储。代码不复杂关键在节奏控制# 主流程 import json import random import time from scraper import create_session, fetch_page from parser import parse_search_page from config import KEYWORDS, MAX_PAGES def main(): session create_session() all_products [] for keyword in KEYWORDS: for page in range(1, MAX_PAGES 1): url fhttps://www.amazon.com/s?k{keyword.replace( , )}page{page}refsr_pg_{page} print(f正在抓取: {keyword} 第{page}页) html fetch_page(session, url) if not html: print(f抓取失败跳过: {keyword} 第{page}页) continue products parse_search_page(html) print(f解析到 {len(products)} 个商品) # 给每条数据打上来源标记 for p in products: p[keyword] keyword p[page] page all_products.extend(products) # 随机延迟模拟人类浏览节奏 delay random.uniform(2, 4) time.sleep(delay) # 保存原始数据 with open(data/raw/products_raw.json, w, encodingutf-8) as f: json.dump(all_products, f, ensure_asciiFalse, indent2) print(f抓取完成共获得 {len(all_products)} 条商品数据) if __name__ __main__: main()这次实际跑下来的结果是5个关键词、每个抓10页共发起约50次请求拿到842条商品数据去重后剩下617条。抓取总耗时约6分钟全程没有出现验证码和封IP的情况。这个稳定性水准基本可以满足日常的竞品监控和选品分析需求了。有人可能会问为什么不做API级别的并发异步。我的回答是亚马逊的页面请求量不需要那么高并发反而高并发会明显增加封号风险。这种数据量级别的项目小步快跑比狂飙突进合适得多。真要跑百万级商品那需要换更重型的方案不是这篇博文讨论的范畴。4. 数据清洗与感恩节商品数据分析4.1 数据清洗流程爬下来的数据不能直接分析还得过一遍清洗。这一步看起来不起眼但直接决定了分析结果的靠谱程度。我在这次项目中主要做了四步清洗第一步是价格字符串转数字。原始数据里价格是$19.99这种文本格式需要去掉美元符号和千位分隔符转成浮点数。这个逻辑简单但容易漏掉$299.00里千位分隔符的处理所以我用了str.replace(,, )先把逗号干掉再转。第二步是评分文本的标准化。亚马逊的评分文本是4.6 out of 5 stars这种格式我通过正则r([\d.])提取前面的数字部分。有的评分字段是4.5有的是4.5 out of 5格式不统一正则一步到位最省心。第三步是折扣力度计算。当original_price和price都非空时折扣力度用(original_price - price) / original_price计算这个值后面会用来判断哪些商品是真促销、哪些是先涨价后打折的套路。顺便说一句亚马逊上有不少商品的划线原价本身就很虚这个字段只做参考不能全信。第四步是去重。同一件商品可能同时命中多个关键词所以按asin做去重保留第一个出现的结果。去重后数据从842条降到了617条去重率接近27%这个数字本身也能说明关键词之间的重叠度。清洗后的代码大致长这样# analyzer.py import pandas as pd import re def clean_data(df): # 删除完全空行 df df.dropna(subset[title]) # 价格转数字 df[price_num] df[price].str.replace($, ).str.replace(,, ).astype(float) # 原价转数字 df[price_org_num] df[original_price].str.replace($, ).str.replace(,, ).astype(float) # 评分转数字 df[rating_num] df[rating].str.extract(r([\d.])).astype(float) # 计算折扣力度没有原价的置为NaN df[discount_rate] (df[price_org_num] - df[price_num]) / df[price_org_num] # 按asin去重 df df.drop_duplicates(subset[asin], keepfirst) return df4.2 分析维度的选择数据洗干净之后我定义了五个分析维度分别回答不同层面的业务问题价格分布统计看的是商品的价格区间分布。感恩节商品里有大量低价小商品也有高客单价的家电厨具价格分布能直观反映市场供给结构。评分排名看的是口碑最好的商品集中在哪些品类、什么价位。如果某类商品评分普遍偏高且评论数多说明这个品类质量成熟、用户认可度高新卖家进入的门槛和机会点跟竞争者数量有关。折扣力度分析是我自己比较看重的一个维度。双十一套路看多了你就知道很多所谓的促销其实是先涨后降。通过对比划线原价和当前价可以识别出哪些商品的折扣是实打实的哪些只是营销噱头。这对消费者是省钱指南对卖家是竞争情报。评论数分布反映商品的受欢迎程度和销售热度。评论数超过1000的商品基本是爆款新品评论数在50到500之间属于潜力款低于50则基本没有市场验证数据支持。关键词重叠度分析则是从抓取的多关键词数据里统计哪些商品同时出现在多个关键词的搜索结果中。这类商品通常是亚马逊系统重点推荐的核心商品商业价值相对明确。4.3 分析结果解读我用pandas做了聚合统计之后有几个发现很有意思。价格分布上感恩节相关商品呈现明显的两极分化。10美元以下的装饰品、餐具类商品占比约28%这类商品主打氛围感购买决策门槛低适合做冲动消费。而100美元以上的厨房电器、空气炸锅、咖啡机等占了约15%这类商品是感恩节大餐场景的核心硬件属于典型的场景刚需。另一个有意思的现象是折扣力度最猛的商品并不在销量最高的区间。20到40美元区间、折扣力度在30%以上的商品评论数普遍在500到2000之间属于腰部爆款。这类商品往往是节日采购的主力价格敏感度适中又有一定的品牌背书是最值得卖家重点研究的对标对象。评分维度上4.5分以上的商品集中在厨房小家电和餐桌用品两类这也符合常识这两个品类用户对好不好用有明确感知评分体系相对可靠。相比之下装饰品类的评分普遍在4.2到4.5之间说明这类商品的主观偏好成分更高。这些分析结论如果做成标准化的周报完全可以支撑一个跨境电商团队在感恩节档期的选品与定价决策。这也是整个项目的最终价值——把海外用户在感恩节买什么、花多少钱、看重什么这个模糊问题转化成了几组清晰的数字。5. 常见问题与排查技巧实录5.1 高频问题与解决方案这个项目在开发和调试过程中我碰到的问题也算典型整理成表格方便查阅问题现象可能原因解决方案请求返回503或Robot Check页面IP被风控识别检查代理IP是否失效更换粘性会话时间窗口降低请求频率页面加载成功但解析结果为空页面结构变化或选择器失效重新用开发者工具定位商品卡片确认>
返回列表