
简介这是一份面向Python初学者与数据分析入门者的实战项目资源聚焦电商数据采集与可视化分析全流程。资源完整实现淘宝热销零食商品的爬取、结构化清洗、地域分布统计及关键词词云生成可直接用于课程设计或自学练手。压缩包共16个文件含2个核心脚本main.py、analysis.py、1个Jupyter Notebook分析示例analysis.ipynb、2个CSV原始与备份数据集、4个Word说明文档含环境配置、运行步骤与免责声明、4个XML工程配置文件及辅助文本文件整体仅3.09MB轻量易部署。已有87人学习下载内容经过作者实测可完整运行代码注释详尽配套文档明确标注依赖库版本与常见问题应对方案还提供售后支持渠道便于调试排错与功能拓展。1. 淘宝零食爬虫清洗词云一套能跑通的课程级实战闭环新手照着改3处就能出图你是不是也试过在B站搜“Python爬虫实战”点开10个视频9个卡在登录验证、1个用的是已失效的selenium旧版驱动或者下载了所谓“完整源码”解压后发现requirements.txt里写着requests2.25.1一装就和新版pandas冲突这套淘宝零食爬虫资源不是玩具——它用真实可复现的路径把「请求构造→反爬绕过→结构化存储→缺失值归因→地理编码补全→词云字体适配」全链路串起来了。我拿它给3届本科生当课程设计模板学生平均2.7小时完成从环境搭建到生成带省份标签的店铺热力图。它不碰登录态、不模拟点击、不走App端加密协议只抓淘宝PC首页“零食”类目下公开的热销榜非搜索结果数据字段明确标注来源如“付款人数”取自商品卡片右下角数字“是否天猫”由店铺logo class判断所有清洗逻辑写在analysis.py里而非黑匣子函数。适合想快速验证数据分析全流程、又不想被验证码/滑块/动态JS拖垮信心的入门者也适合需要交付型作业的在校生——文档里连PyCharm调试断点位置都标好了。2. 爬虫实现为什么不用Selenium而选RequestsBeautifulSoup以及如何绕过淘宝基础反爬2.1 请求头与Referer策略淘宝PC端的“透明门禁”淘宝对未携带Referer的直接GET请求会返回302跳转到首页这不是严格意义上的反爬而是前端路由保护。本项目用requests构造请求时强制设置headers包含Referer和User-Agent且Referer必须是淘宝搜索页URL格式headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36, Referer: https://s.taobao.com/search?q%E9%9B%B6%E9%A3%9Fimgfilejs1stats_clicksearch_radio_all%3A1initiative_idstaobaoz_20231015ieutf8 }提示Referer中的q参数必须URL编码零食对应%E9%9B%B6%E9%A3%9F不能直接写中文。initiative_id值可任意但格式必须为staobaoz_YYYYMMDD否则部分商品卡片不渲染。关键点在于这个Referer不是随便填的它是淘宝搜索页的真实URL结构。项目中main.py第42行通过urllib.parse.quote()动态编码关键词确保每次请求都带合法Referer。如果你改成https://www.taobao.com服务器会静默丢弃请求——没有报错但response.text里只有首页HTML根本找不到商品列表。2.2 解析逻辑用CSS选择器定位而非XPath规避动态class名漂移淘宝商品列表的HTML结构每季度会微调比如.item.J_MouserOnverReq可能变成.item.J_ItemList但核心容器#mainsrp-itemlist .items始终稳定。项目采用CSS选择器组合定位比XPath更耐变化# main.py 第68行精准定位商品卡片 items soup.select(#mainsrp-itemlist .items .item.J_MouserOnverReq) for item in items: # 提取价格优先找 .price strong fallback到 .price em price_elem item.select_one(.price strong) or item.select_one(.price em) price float(price_elem.get_text(stripTrue).replace(¥, ).replace(,, )) if price_elem else None # 提取销量匹配月销XXX件或已售XXX件文本 sales_text item.select_one(.deal-cnt).get_text(stripTrue) if item.select_one(.deal-cnt) else sales int(re.search(r(\d)件, sales_text).group(1)) if re.search(r(\d)件, sales_text) else 0这里有两个设计细节价格提取双保险.price strong是当前主流结构但历史版本用.price em代码用or兜底销量正则容错deal-cnt元素内文本可能是“月销1.2万件”或“已售567件”正则r(\d)件只捕获纯数字避免int(1.2万)报错。这种写法比硬编码XPath路径如//div[classprice]/strong/text()抗干扰性强——我去年用同一套逻辑跑过京东零食榜只改了2行选择器就迁过去了。2.3 动态参数处理如何应对淘宝分页URL的token校验淘宝PC端分页不是简单?s44递增而是带tab、sort、bcoffset等参数其中bcoffset随页码变化且有校验逻辑。项目没硬解算法而是用“页面滚动触发加载”的思路首次请求获取第1页HTML从HTML中提取input idq value零食的搜索词构造下一页URL时固定bcoffset0靠s44每页44条推进# main.py 第112行分页URL生成 base_url https://s.taobao.com/search params { q: urllib.parse.quote(keyword), s: str(page * 44), # page从0开始第1页s0第2页s44 tab: all, sort: default, bcoffset: 0, # 固定值淘宝实际校验宽松 } url base_url ? urllib.parse.urlencode(params)实测有效抓取前5页220条商品成功率99.2%失败的3条是因商品下架导致卡片DOM缺失不影响整体流程。这比逆向分析bcoffset生成规则需JS执行更轻量也符合“课程设计”定位——你要教学生的是工程思维不是密码学。3. 数据清洗从原始CSV到可分析数据集的四步归因法3.1 缺失值诊断为什么product_info.csv里有23%的“店铺城市”为空打开product_info.csv你会发现shop_city列大量为空字符串。这不是爬虫漏了而是淘宝PC端商品页不显示店铺城市——只显示“浙江杭州”或“广东深圳”这类省份城市组合但部分店铺尤其个体户只填了省份。项目在analysis.py第87行做了归因清洗# analysis.py 第87行店铺城市缺失值归因 df[shop_province] df[shop_location].str.extract(r^(.*?)(?:省|自治区|直辖市)) df[shop_city] df[shop_location].str.extract(r^(?:.*?省|.*?自治区|.*?直辖市)(.*?市|.*?区|.*?县)) # 对shop_city为空的行用高德API补全需申请key项目默认关闭 # 此处用规则补全若shop_location含杭州则city杭州含深圳则city深圳 city_keywords [杭州, 深圳, 广州, 成都, 武汉, 西安, 重庆] for kw in city_keywords: mask (df[shop_city].isna()) (df[shop_location].str.contains(kw)) df.loc[mask, shop_city] kw关键洞察缺失不是错误是信息粒度差异。淘宝允许店铺只填省份所以shop_location字段本身就有两种模式“浙江省杭州市” vs “广东省”。清洗时先用正则拆解再用关键词兜底比直接删行或填“未知”更有业务意义——后续做省份热力图时shop_province完整率100%shop_city完整率提升到89%。3.2 销量单位标准化把“1.2万件”、“567件”、“200人付款”统一成整数原始数据中sales字段混杂三种格式月销1.2万件→ 需转为12000已售567件→ 转为567200人付款→ 转为200号表示约数取下限analysis.py第124行用状态机处理def parse_sales(text): if not isinstance(text, str): return 0 # 匹配X万Y格式如1.2万 wan_match re.search(r(\d\.?\d*)万, text) if wan_match: return int(float(wan_match.group(1)) * 10000) # 匹配纯数字件或如567件、200 num_match re.search(r(\d)(?:件|\), text) return int(num_match.group(1)) if num_match else 0 df[sales_clean] df[sales].apply(parse_sales)注意re.search(r(\d\.?\d*)万, text)中\.表示匹配小数点\d*允许无小数位如5万这是针对淘宝文案的定制化正则——通用爬虫库不会预设“万”单位转换。3.3 价格异常值过滤为什么均价35元的零食里混进了一台iPhone检查product_info.csv你会发现price列有5999.0这样的值。这不是爬虫错而是淘宝“零食”类目下混入了跨界商品如零食大礼包含iPhone抽奖。项目用IQR四分位距法过滤# analysis.py 第156行价格异常值剔除 Q1 df[price].quantile(0.25) Q3 df[price].quantile(0.75) IQR Q3 - Q1 lower_bound Q1 - 1.5 * IQR upper_bound Q3 1.5 * IQR df_clean df[(df[price] lower_bound) (df[price] upper_bound)] print(f剔除{len(df)-len(df_clean)}条价格异常记录)计算得upper_bound ≈ 128.5所有price 128.5的商品含iPhone、高端咖啡机被剔除。这比简单设price 100更科学——它基于本次数据分布动态计算下次抓“进口巧克力”时阈值自动上浮。4. 词云生成避开微软雅黑字体报错、中文乱码、停用词漏筛三大坑4.1 字体路径硬编码为什么你的词云全是方框analysis.ipynb第32行指定字体路径wc WordCloud( font_pathsimhei.ttf, # 关键必须是绝对路径或同目录下的ttf文件 width800, height400, background_colorwhite, max_words100 )但simhei.ttf黑体在Windows常见macOS/Linux默认没有。项目在code.zip根目录放了simhei.ttf但如果你解压后移动了文件夹相对路径就失效。血泪经验运行前先确认os.getcwd()返回路径下存在该文件import os font_path simhei.ttf if not os.path.exists(font_path): raise FileNotFoundError(f字体文件 {font_path} 不存在请检查解压路径)注意不要用matplotlib.font_manager.findfont(SimHei)它返回系统字体路径但淘宝商品名含emoji如“薯片”系统字体常不支持反而导致词云崩溃。4.2 中文分词陷阱jieba默认切分“奥利奥”会切成“奥 利 奥”analysis.py第201行用jieba精确模式但加了自定义词典import jieba # 加载自定义词典防止零食名被误切 jieba.load_userdict(snack_keywords.txt) # 文件含奥利奥,乐事,卫龙等品牌名 # 分词并过滤停用词 stopwords set([的, 和, 在, 上, 与, 及, 等, 款, 盒, 袋, 包]) words [] for title in df_clean[title]: seg_list jieba.lcut(title) words.extend([w for w in seg_list if w not in stopwords and len(w) 1])snack_keywords.txt内容示例奥利奥 乐事 卫龙 三只松鼠 百草味 良品铺子没有这个词典jieba.lcut(奥利奥饼干)输出[奥, 利, 奥, 饼干]词云里全是单字——完全失真。而加了词典后输出[奥利奥, 饼干]这才是业务需要的颗粒度。4.3 词频统计偏差为什么“零食”出现次数最多却不出现在词云因为WordCloud默认过滤停用词但零食不在默认停用词表里。项目在analysis.py第215行手动排除# 过滤业务无关高频词 business_stopwords [零食, 食品, 旗舰店, 专营店, 官方, 正品] words_filtered [w for w in words if w not in business_stopwords]否则词云中心会是巨大的“零食”二字失去分析价值。这体现了领域停用词必须人工定义——通用停用词表解决不了垂直场景问题。5. 避坑指南爬虫运行失败、数据空、词云空白的5个真实翻车现场5.1 现象main.py运行后product_info.csv只有表头无数据原因淘宝PC端HTML结构更新#mainsrp-itemlist .items .item.J_MouserOnverReq选择器失效soup.select()返回空列表。解决打开https://s.taobao.com/search?q零食按F12检查商品卡片最外层div的class名替换main.py第68行的选择器。例如新结构是.item.J_ItemList则改为soup.select(#mainsrp-itemlist .items .item.J_ItemList)。5.2 现象analysis.ipynb报错UnicodeDecodeError: gbk codec cant decode byte 0x80原因product_info.csv用UTF-8保存但Windows记事本默认用GBK打开pandas.read_csv()未指定encoding时尝试GBK解码失败。解决在analysis.ipynb第10行显式指定编码df pd.read_csv(product_info.csv, encodingutf-8)5.3 现象词云图片全白无任何文字原因simhei.ttf字体文件损坏或路径错误WordCloud无法加载字体降级为默认字体不支持中文所有字符渲染为空格。解决用fontTools验证字体pip install fonttools然后运行from fontTools.ttLib import TTFont TTFont(simhei.ttf) # 若报错则文件损坏需重新下载确认simhei.ttf与analysis.py在同一目录。5.4 现象销量清洗后sales_clean列全为0原因原始sales字段值为None或空字符串parse_sales()函数中re.search返回Noneint(None)报错但代码用try-except吞掉了异常返回0。解决在parse_sales()开头加日志print(f解析销量原文: {text}) # 临时加这行看哪些文本无法匹配常见问题sales列含-或暂无需在正则前加预处理text text.replace(-, ).replace(暂无, )5.5 现象店铺省份分布图显示“其他”占比80%原因shop_location字段含大量“海外”、“港澳台”、“保税区”等非省级行政区划正则r^(.*?)(?:省|自治区|直辖市)无法匹配。解决扩展省份映射表在analysis.py第95行补充# 手动映射特殊区域 province_map { 海外: 海外, 港澳台: 港澳台, 保税区: 保税区, 义乌: 浙江, # 义乌属浙江金华 东莞: 广东 } df[shop_province] df[shop_province].map(province_map).fillna(df[shop_province])6. 进阶技巧用地理编码补全省份坐标生成带经纬度的店铺热力图6.1 为什么单纯省份统计不够——从“浙江最多”到“杭州西湖区最密集”analysis.py默认只输出省份频次柱状图但业务决策需要空间精度。比如“浙江”有120家店铺但其中83家在杭州21家在温州——这直接影响仓储选址。项目预留了高德地理编码接口虽默认关闭避免API Key泄露但提供了完整调用框架# analysis.py 第250行地理编码函数需自行申请Key def get_coordinates(address, keyYOUR_AMAP_KEY): url fhttps://restapi.amap.com/v3/geocode/geo params {address: address, key: key, city: 全国} try: res requests.get(url, paramsparams, timeout5) data res.json() if data[status] 1 and data[count] ! 0: location data[geocodes][0][location] # 120.155123,30.274084 return tuple(map(float, location.split(,))) except Exception as e: print(f地理编码失败 {address}: {e}) return (0, 0) # 批量获取坐标示例前10家杭州店铺 sample_shops df_clean[df_clean[shop_city] 杭州].head(10) sample_shops[coordinates] sample_shops[shop_location].apply( lambda x: get_coordinates(x) )提示高德免费额度1万次/日足够课程设计使用。address参数用shop_location如“浙江杭州”比单用shop_city“杭州”更准避免重名城市如“吉林市”vs“吉林省”。6.2 热力图实现用folium绘制交互式地图比静态图多3个实用功能analysis.ipynb第55行用folium替代matplotlib绘图优势在于功能静态图matplotlib交互式热力图folium缩放/平移❌✅ 鼠标滚轮即可悬停查看详情❌✅ 显示店铺名、销量导出为HTML❌需截图✅ 直接保存为网页文件import folium from folium.plugins import HeatMap # 创建地图以中国为中心 m folium.Map(location[35.0, 105.0], zoom_start3) # 准备热力图数据[[纬度, 经度, 权重], ...] heat_data [] for _, row in sample_shops.iterrows(): if row[coordinates] ! (0, 0): # 过滤编码失败 heat_data.append([ row[coordinates][1], # 纬度 row[coordinates][0], # 经度 row[sales_clean] # 权重销量 ]) HeatMap(heat_data, radius15, blur10).add_to(m) m.save(shop_heatmap.html) # 生成可交互HTMLradius15控制热力点半径像素blur10控制模糊程度——值越大越平滑适合宏观分布值越小越聚焦适合识别集群。我一般设radius8, blur5既能看清杭州西湖区密集点又不淹没温州、宁波的次级集群。6.3 词云进阶心形词云白底只需改3行代码热搜词里有“心形词云 白底”这需求很常见。analysis.ipynb第40行原词云是矩形要变心形只需安装PIL和numpy已含在requirements.txt下载心形mask图黑白PNG心形为白色背景为黑色修改WordCloud参数from PIL import Image import numpy as np # 加载心形mask需提前准备heart_mask.png heart_mask np.array(Image.open(heart_mask.png)) wc WordCloud( font_pathsimhei.ttf, maskheart_mask, # 关键指定mask background_colorwhite, # 白底 contour_width1, # 心形边缘描边 contour_colorred # 描边红色 )注意mask图必须是纯黑白灰色会导致词云边缘毛刺。用Photoshop或在线工具如remove.bg抠图后另存为PNG用Image.open().convert(L)转灰度再np.array()即可。从那以后我每次做词云都强制走一遍mask校验流程先用plt.imshow(heart_mask, cmapgray)确认mask是纯黑白再跑词云。曾经因mask含Alpha通道词云中心出现诡异的透明圆环debug了2小时才发现是PNG保存选项错了。希望帮到你。本文还有配套的精品资源点击获取