ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

用Python多因子评分模型解决选择困难症:从爬虫到OpenCV图像分析实战

用Python多因子评分模型解决选择困难症:从爬虫到OpenCV图像分析实战 事情是这样的女朋友生日前一周给我甩过来一批购物车截图说“你帮我挑一条黑丝吧你眼光好”。我盯着手机里十几张看起来一模一样的商品图说实话肉眼真的分不出哪款跟哪款有什么本质区别。这种时刻作为一个日常写Python的人我下意识的第一反应不是硬着头皮瞎猜而是——能不能把这活儿交给代码来干。我当时的思路很简单审美这件事虽然主观但它一定有可量化的维度。版型、透肉程度、光泽、用户口碑、日常搭配的百搭性这些东西都能变成数据。既然能变成数据就能用Python来采集、分析、打分最后输出一个“综合推荐指数”。这篇文章就把我整套分析流程拆开写清楚从爬虫取数、OpenCV图像特征提取、NLP情感分析到多因子评分模型和可视化完整复现一遍。如果你也想用技术手段解决类似“选择困难症”或者单纯想练手Python爬虫、图像处理、数据分析这几个方向这篇应该能给你一个挺完整的参考。整个项目并不复杂但串联的知识点很密集而且有几个环节不实操的话真的发现不了坑。下面直接进入正题。1. 先拆解问题把“好看”翻译成Python能算的指标在我写第一行代码之前先做了一件最关键的事把“哪一款好看”这个主观问题拆成Python能理解和计算的客观指标。这一步决定了后面所有代码的方向不能跳过。我最后拆出了五个维度版型设计高腰还是中腰腰部是否有宽边设计这些信息通过商品标题和商品描述里的关键词来提取。透肉程度这基本是黑丝最核心的视觉特征。透肉感强的偏性感不透的偏保暖实用。这个可以通过图像处理计算图片里“肤色像素”的占比来近似估计。光泽与纹理哑光和微光泽的质感完全不同可以用图像梯度、颜色直方图分布来做粗粒度辨识。用户口碑评论区里高频出现的形容词比如“显瘦”“百搭”“不勾丝”“掉裆”“太透”等通过NLP情感分析和关键词分类来量化。价格带与销量不算绝对指标但可以反映“大众用脚投票”的结果作为加权因子。这里我特别想强调一个认知选品类问题本质上是多因子打分问题。如果你真的去问一个穿搭博主“哪款好看”她说出来的理由拆开也无非是这几个维度。Python能做的就是把博主脑子里的那套经验判断变成一台可复现的计算器。拆完维度之后我给每个维度预设一个权重区间比如透肉程度在夏天权重高一些在冬天保暖性权重提高。这个先不急等数据出来再调但维度框架必须先定死不然后面会变成无头苍蝇。另外一点值得说这个思路完全不局限于挑黑丝。你把“黑丝”换成“口红”“球鞋”“蓝牙耳机”流程一模一样。先拆指标再找数据源再量化再打分。这是这套方法真正的价值所在。2. 数据采集商品图爬取与评论区的清洗逻辑维度拆好之后下一步是数据从哪来。我选择的是电商平台的公开商品数据包括商品主图和评论内容。这个环节有两个任务批量下载商品图片以及把评论区文本抓下来清洗成可分析的结构化数据。2.1 环境准备与关键依赖先说环境。我用的是Python 3.10建议读者不要用太老的版本3.8以上就行。依赖库主要这几个pip install requests beautifulsoup4 lxml opencv-python numpy pandas jieba snownlp pyecharts如果你在安装opencv-python时速度很慢或者超时建议配置国内镜像源实测有明显改善pip install opencv-python -i https://pypi.tuna.tsinghua.edu.cn/simple不配置镜像源的话opencv这种大包很容易卡在半路我最早踩过一次后来学乖了。2.2 商品图批量采集电商平台的页面结构相对规整我用requests加BeautifulSoup就能搞定。核心思路是先请求商品搜索页解析出商品详情页链接再进入详情页提取主图地址最后把图片二进制流落盘。下面是核心代码框架我做了简化但思路是完整的import requests from bs4 import BeautifulSoup import os import time 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 } def get_html(url): resp requests.get(url, headersHEADERS, timeout10) resp.encoding utf-8 return resp.text def parse_product_list(html): soup BeautifulSoup(html, lxml) links [] for a in soup.select(a[href*item.htm]): href a.get(href) if href and href.startswith(//): href https: href links.append(href) return list(set(links)) def download_image(img_url, save_path): if not img_url.startswith(http): img_url https: img_url resp requests.get(img_url, headersHEADERS, timeout10) with open(save_path, wb) as f: f.write(resp.content) # 伪代码示意对每个商品详情页提取主图URL for idx, product_url in enumerate(product_links[:20]): html get_html(product_url) soup BeautifulSoup(html, lxml) img_tag soup.find(img, idJ_ImgBooth) if img_tag: img_url img_tag.get(data-src) or img_tag.get(src) download_image(img_url, fimages/{idx}.jpg) time.sleep(2)这里有几个“说多了都是泪”的细节大部分平台对单张图片的URL做了懒加载处理真实地址在>import pandas as pd import re def clean_comment(raw_text): # 去除HTML标签和特殊符号 text re.sub(r[^], , raw_text) text re.sub(r[^\u4e00-\u9fa5a-zA-Z0-9\s], , text) text re.sub(r\s, , text).strip() return text comments [] for comment in raw_comment_list: cleaned clean_comment(comment) if len(cleaned) 2: comments.append(cleaned) df pd.DataFrame(comments, columns[comment]) df.to_csv(comments.csv, indexFalse, encodingutf-8-sig)注意输出时用utf-8-sig而不是utf-8因为Excel直接打开utf-8文件会乱码。这个细节我踩过一次坑后来就长记性了。3. OpenCV图像特征提取从像素到审美参数图片数据准备好之后进入整个项目最核心的技术环节用OpenCV把商品的图像特征转成数字。这步如果做得好后面的评分模型才有依据。3.1 颜色直方图先判断整体色调黑丝商品图的主色调理论上应该是黑色和肤色两种。但不同商品图的色调有偏色现象有的偏灰有的偏蓝。我用HSV颜色空间做直方图统计可以快速判断整体色调分布。import cv2 import numpy as np def dominant_hue_histogram(image_path): img cv2.imread(image_path) img_hsv cv2.cvtColor(img, cv2.COLOR_BGR2HSV) # 统计饱和度较低的像素比例用来排除纯白色背景干扰 mask cv2.inRange(img_hsv, (0, 0, 0), (180, 255, 255)) hist cv2.calcHist([img_hsv], [0], mask, [180], [0, 180]) return hist这里有个关键知识点OpenCV读取图片的通道顺序是BGR而不是RGB如果你直接用RGB去做颜色判断黑色和深灰色容易混因为RGB三个通道的数值差异会被放大。转到HSV空间之后色调、饱和度、明度分开逻辑就清晰很多。很多新手在这里容易犯迷糊我建议你把读进来的图先cv2.cvtColor转到HSV再处理能省掉大量调试时间。3.2 肤色检测估算“透肉感”透肉程度是这类商品最核心的视觉属性我的思路是在商品图的腿部区域统计肤色像素的占比。肤色在HSV空间里有一个相对稳定的范围我用以下方式做检测def skin_ratio(image_path): img cv2.imread(image_path) if img is None: return None img_hsv cv2.cvtColor(img, cv2.COLOR_BGR2HSV) # 肤色大致范围需要根据实际图片微调 lower_skin np.array([0, 20, 70], dtypenp.uint8) upper_skin np.array([25, 160, 255], dtypenp.uint8) mask cv2.inRange(img_hsv, lower_skin, upper_skin) ratio cv2.countNonZero(mask) / (img.shape[0] * img.shape[1]) return ratio肤色检测准确率很受商品图光线影响。图片偏暗的时候肤色像素会被压到阈值之外导致“透肉度”被低估。我实测最好的办法是多准备几张不同角度的商品图取平均值而不是只拿一张主图判断。这个处理对最终评分的稳定性很关键。3.3 纹理与光泽拉普拉斯方差黑丝的“质感”在图像上体现为纹理细节。丝袜表面如果光滑有光泽梯度变化相对均匀如果是哑光材质纹理响应也不一样。我用拉普拉斯算子计算图像方差作为“细节丰富度”的粗略评分。def texture_score(image_path): img cv2.imread(image_path, cv2.IMREAD_GRAYSCALE) if img is None: return None laplacian cv2.Laplacian(img, cv2.CV_64F) score laplacian.var() return score这个score本身不直接等于“好看”但可以做横向对比。同一类商品图里纹理分明显异常高的往往是图片噪点太多或者过度锐化不一定是质感好。我会把它作为过滤异常图片的一个参考维度而不是直接进评分模型。3.4 批量处理与结果落表所有图片都跑一遍上述特征最后汇成一张特征表商品ID肤色像素占比纹理评分色调均值(HSV)明度均值0010.23158.323046.50020.31142.722552.10030.08171.224038.9到这一步“透肉程度”和“质感”已经从图片变成了数值。接下来需要处理另一个重要维度——用户口碑。4. 评论文本的情感分析用NLP量化“买家秀”口碑图像特征解决了“看起来怎么样”的问题但“穿起来怎么样”还得看评论区。这一节处理评论文本把散乱的用户反馈变成可计算的分数。4.1 分词提取找出高频关键词我先把评论用jieba分词然后统计高频词。高频词能很直观地反映用户最在意的点。import jieba from collections import Counter df pd.read_csv(comments.csv) words [] for comment in df[comment].dropna(): seg_list jieba.lcut(comment) words.extend([w for w in seg_list if len(w) 1]) counter Counter(words) top_words counter.most_common(50)执行之后高频词就像一个“投票结果”显瘦、百搭、不透、舒服这类词出现频次越高说明这款商品在这几个维度上得到了更多认可。4.2 情感得分SnowNLP计算正负倾向高频词只是局部视角我再用SnowNLP给每条评论算一个情感倾向分取值在0到1之间越接近1越正面越接近0越负面。from snownlp import SnowNLP def sentiment_score(comment): s SnowNLP(comment) return s.sentiments df[sentiment] df[comment].apply(sentiment_score) avg_score df[sentiment].mean()这里有个很关键的坑SnowNLP的默认模型对“黑丝”这个垂直场景并不敏感。比如“太透了不日常”这句话情感倾向其实是中偏负但SnowNLP可能只给到0.4左右并不会给出特别低的分数。所以情感分只能作为参考因子权重不能给太高。我处理的方式是再叠加一个“负面关键词命中数”指标如果评论中包含“掉裆”“起球”“勾丝”“太透”“质量差”等词直接扣分包含“显瘦”“百搭”“舒服”“回购”等词额外加分。相当于用规则补充模型的盲区。4.3 构建评论维度评分最后我把评论文本转成三个分数好评率情感分大于0.6的评论占比。负面命中率包含负面关键词的评论占比越低越好。核心卖点命中率包含“显瘦”或“百搭”的评论占比。这三个分数做归一化之后就是“口碑维度”的最终得分。代码不算复杂但逻辑比单纯的情感分要稳得多。实际踩完这套流程我才明白NLP落地的难点不在于模型多高级而在于怎么把模型结果和业务规则结合起来。5. 多因子评分模型权重怎么定结果怎么可视化所有数据都齐了现在进入“算总账”的环节。图像特征、评论口碑、价格销量这些数据量纲完全不一样不能直接相加必须先做归一化再按权重加权。5.1 归一化处理我用最简单的min-max归一化把所有分数压到0到1之间def minmax_normalize(series): return (series - series.min()) / (series.max() - series.min())归一化之后每个维度都在同一量纲下权重才有意义。5.2 权重设置与实际计算我给五个维度分配了权重这个权重不是随便拍的而是在跑了几轮之后根据合理性调的维度权重说明透肉程度0.25黑丝的核心视觉属性用户最关注口碑好感度0.30实际穿着的反馈占比最高图像质感0.15光泽与纹理的视觉品质版型关键词0.20高腰/中腰/宽边等版型偏好价格与销量0.10性价比参考权重最低这里必须说一句权重本质上是一个偏好设置不是算法给的客观答案。如果你自己给女朋友买她更看重保暖还是更看重透肉权重分配会完全不一样。所以我把权重做成可配置的字典方便随时调整。weights { skin_ratio: 0.25, sentiment: 0.30, texture: 0.15, style_keyword: 0.20, price_sales: 0.10 } df[final_score] ( df[skin_ratio_norm] * weights[skin_ratio] df[sentiment_norm] * weights[sentiment] df[texture_norm] * weights[texture] df[style_keyword_norm] * weights[style_keyword] df[price_sales_norm] * weights[price_sales] )我把每个商品的分项得分和最终得分汇总成一张总表按最终得分倒序排列排第一的就是“综合推荐款”。5.3 pyecharts可视化结果光是Excel表格还不够直观我用pyecharts做了一个雷达图把Top3商品放到一张图上对比。from pyecharts import options as opts from pyecharts.charts import Radar def make_radar(data): radar Radar() radar.add_schema( schema[ opts.RadarIndicatorItem(name透肉, max_1), opts.RadarIndicatorItem(name口碑, max_1), opts.RadarIndicatorItem(name质感, max_1), opts.RadarIndicatorItem(name版型, max_1), opts.RadarIndicatorItem(name性价比, max_1) ] ) radar.add(商品A, data) return radar雷达图在屏幕上铺开的那一刻视觉冲击力很强女朋友看到也会觉得这波分析“有点东西”。但有一句说一句可视化是辅助核心还是评分逻辑的公允性。图做得再炫评分逻辑是扯淡的那就是自嗨。6. 跑完整个流程我踩过的几个关键坑这个项目整体不算难但分布式的小坑特别多。单拎出来每一个都不致命串联起来却能让人抓狂。我把印象最深的几个列出来你们能少走点弯路。6.1 OpenCV读不了中文路径这是最经典的一个坑。如果你的图片路径或者文件名包含中文cv2.imread()直接返回None而且不报错。我一开始还以为是图片下载出了问题排查了半天才发现是路径编码问题。解决方案有两种一是把文件路径改成英文二是用cv2.imdecode读取二进制流绕开中文路径问题def cv2_imread_chinese(path): img_bytes np.fromfile(path, dtypenp.uint8) return cv2.imdecode(img_bytes, cv2.IMREAD_COLOR)我后来直接用第二种方式一劳永逸。6.2 图片懒加载导致抓下来的全是占位图前面提到过商品主图经常在>def normalize_brightness(img, target_mean128): hsv cv2.cvtColor(img, cv2.COLOR_BGR2HSV) h, s, v cv2.split(hsv) current_mean v.mean() v cv2.add(v, int(target_mean - current_mean)) hsv cv2.merge([h, s, v]) return cv2.cvtColor(hsv, cv2.COLOR_HSV2BGR)这个小函数看似不起眼却直接决定了“透肉度”这个核心指标的准确性。6.5 不要迷信“评分最高”最后说一个方法论上的坑。打分模型输出的第一名不一定是你最后应该买的那款。模型没有考虑到你和她的具体场景比如她怕冷、她想要一款能穿去正式场合的、她腿型偏好高腰还是中腰。模型给你的是“数据支持”不是“最终决策”。我的处理方式是把评分结果作为候选清单让女朋友最终拍板。她如果选了第二名我也不会觉得模型出了问题因为审美和偏好永远是个性化的。7. 一点经验之谈这个项目做完之后我最大的感受是技术本身不复杂复杂的是把现实问题翻译成技术问题的过程。你问“哪款黑丝好看”本质上是一个多因子决策问题你问“Python能不能解决选择困难症”答案是可以但模型只能辅助不能替代人的判断。如果你想在这个基础上继续扩展可以尝试的方向挺多接入更多的商品数据源优化肤色检测的模型比如用深度学习分割出腿部区域再检测或者用大模型微调一个更懂穿搭子领域的评论情感分析模型。每一条路都有得玩也都能让你在Python的某个细分方向上走得更深。回到最开头的问题最终我推荐的那款女朋友满不满意她看完我的评分雷达图点了点头然后自己下单了两款——评分第一和评分第三。原因是第二款有她喜欢的颜色而这个维度我压根没建模。这大概是所有技术人做生活项目都会遇到的经典结局模型永远不会完整但这并不妨碍我们用一套系统化的思考方式把“瞎选”变成“有依据地选”。这就是我认为这个项目最大的价值。
返回列表