标签趋势自然语言进行搜索
This commit is contained in:
parent
4a2e7f1222
commit
5b9b6da042
@ -0,0 +1,23 @@
|
|||||||
|
# Generated by Django 5.2.1 on 2025-05-23 08:37
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('daren_detail', '0002_remove_creatorprofile_pricing_max_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='creatorprofile',
|
||||||
|
name='hashtags',
|
||||||
|
field=models.TextField(blank=True, help_text='以#分隔的标签,例如:#fashion#beauty#lifestyle', null=True, verbose_name='标签'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='creatorprofile',
|
||||||
|
name='trends',
|
||||||
|
field=models.TextField(blank=True, help_text='创作者相关的趋势关键词', null=True, verbose_name='趋势'),
|
||||||
|
),
|
||||||
|
]
|
18
apps/daren_detail/migrations/0004_creatorprofile_profile.py
Normal file
18
apps/daren_detail/migrations/0004_creatorprofile_profile.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 5.2.1 on 2025-05-23 09:11
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('daren_detail', '0003_creatorprofile_hashtags_creatorprofile_trends'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='creatorprofile',
|
||||||
|
name='profile',
|
||||||
|
field=models.CharField(choices=[('tiktok', 'TikTok'), ('instagram', 'Instagram'), ('youtube', 'YouTube'), ('xiaohongshu', '小红书'), ('other', '其他平台')], default='tiktok', max_length=20, verbose_name='达人平台'),
|
||||||
|
),
|
||||||
|
]
|
@ -122,6 +122,20 @@ class CreatorProfile(models.Model):
|
|||||||
location = models.CharField(max_length=100, blank=True, null=True, verbose_name="位置")
|
location = models.CharField(max_length=100, blank=True, null=True, verbose_name="位置")
|
||||||
live_schedule = models.CharField(max_length=255, blank=True, null=True, verbose_name="直播时间表")
|
live_schedule = models.CharField(max_length=255, blank=True, null=True, verbose_name="直播时间表")
|
||||||
|
|
||||||
|
# 新增达人平台字段
|
||||||
|
PROFILE_CHOICES = [
|
||||||
|
('tiktok', 'TikTok'),
|
||||||
|
('instagram', 'Instagram'),
|
||||||
|
('youtube', 'YouTube'),
|
||||||
|
('xiaohongshu', '小红书'),
|
||||||
|
('other', '其他平台'),
|
||||||
|
]
|
||||||
|
profile = models.CharField(max_length=20, choices=PROFILE_CHOICES, default='tiktok', verbose_name="达人平台")
|
||||||
|
|
||||||
|
# 新增hashtag和trend字段
|
||||||
|
hashtags = models.TextField(blank=True, null=True, verbose_name="标签", help_text="以#分隔的标签,例如:#fashion#beauty#lifestyle")
|
||||||
|
trends = models.TextField(blank=True, null=True, verbose_name="趋势", help_text="创作者相关的趋势关键词")
|
||||||
|
|
||||||
# 类别 - Category
|
# 类别 - Category
|
||||||
CATEGORY_CHOICES = [
|
CATEGORY_CHOICES = [
|
||||||
('Phones & Electronics', '手机与电子产品'),
|
('Phones & Electronics', '手机与电子产品'),
|
||||||
|
@ -0,0 +1,23 @@
|
|||||||
|
# Generated by Django 5.2.1 on 2025-05-23 08:37
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('discovery', '0002_alter_searchsession_date_created'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='creator',
|
||||||
|
name='hashtags',
|
||||||
|
field=models.TextField(blank=True, null=True, verbose_name='标签'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='creator',
|
||||||
|
name='trends',
|
||||||
|
field=models.TextField(blank=True, null=True, verbose_name='趋势'),
|
||||||
|
),
|
||||||
|
]
|
18
apps/discovery/migrations/0004_creator_profile.py
Normal file
18
apps/discovery/migrations/0004_creator_profile.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 5.2.1 on 2025-05-23 09:11
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('discovery', '0003_creator_hashtags_creator_trends'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='creator',
|
||||||
|
name='profile',
|
||||||
|
field=models.CharField(choices=[('tiktok', 'TikTok'), ('instagram', 'Instagram'), ('youtube', 'YouTube'), ('xiaohongshu', '小红书'), ('other', '其他平台')], default='tiktok', max_length=20, verbose_name='达人平台'),
|
||||||
|
),
|
||||||
|
]
|
@ -57,6 +57,14 @@ class Creator(models.Model):
|
|||||||
('Home Supplies', 'Home Supplies'),
|
('Home Supplies', 'Home Supplies'),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
PROFILE_CHOICES = [
|
||||||
|
('tiktok', 'TikTok'),
|
||||||
|
('instagram', 'Instagram'),
|
||||||
|
('youtube', 'YouTube'),
|
||||||
|
('xiaohongshu', '小红书'),
|
||||||
|
('other', '其他平台'),
|
||||||
|
]
|
||||||
|
|
||||||
session = models.ForeignKey(SearchSession, on_delete=models.CASCADE, related_name='creators', verbose_name="所属会话")
|
session = models.ForeignKey(SearchSession, on_delete=models.CASCADE, related_name='creators', verbose_name="所属会话")
|
||||||
name = models.CharField(max_length=100, verbose_name="创作者名称")
|
name = models.CharField(max_length=100, verbose_name="创作者名称")
|
||||||
avatar = models.URLField(blank=True, null=True, verbose_name="头像URL")
|
avatar = models.URLField(blank=True, null=True, verbose_name="头像URL")
|
||||||
@ -69,6 +77,9 @@ class Creator(models.Model):
|
|||||||
avg_video_views = models.FloatField(default=0, verbose_name="平均视频观看量")
|
avg_video_views = models.FloatField(default=0, verbose_name="平均视频观看量")
|
||||||
has_ecommerce = models.BooleanField(default=False, verbose_name="是否有电商")
|
has_ecommerce = models.BooleanField(default=False, verbose_name="是否有电商")
|
||||||
tiktok_url = models.URLField(blank=True, null=True, verbose_name="抖音链接")
|
tiktok_url = models.URLField(blank=True, null=True, verbose_name="抖音链接")
|
||||||
|
hashtags = models.TextField(blank=True, null=True, verbose_name="标签")
|
||||||
|
trends = models.TextField(blank=True, null=True, verbose_name="趋势")
|
||||||
|
profile = models.CharField(max_length=20, choices=PROFILE_CHOICES, default='tiktok', verbose_name="达人平台")
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = "创作者"
|
verbose_name = "创作者"
|
||||||
|
@ -152,6 +152,9 @@ class CreatorDiscoveryViewSet(viewsets.ReadOnlyModelViewSet):
|
|||||||
category = request.data.get('category', None)
|
category = request.data.get('category', None)
|
||||||
ecommerce_level = request.data.get('ecommerce_level', None)
|
ecommerce_level = request.data.get('ecommerce_level', None)
|
||||||
exposure_level = request.data.get('exposure_level', None)
|
exposure_level = request.data.get('exposure_level', None)
|
||||||
|
hashtag = request.data.get('hashtag', None)
|
||||||
|
trend = request.data.get('trend', None)
|
||||||
|
profile = request.data.get('profile', None) # 新增profile参数
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# 导入CreatorProfile模型
|
# 导入CreatorProfile模型
|
||||||
@ -197,39 +200,95 @@ class CreatorDiscoveryViewSet(viewsets.ReadOnlyModelViewSet):
|
|||||||
query_filters['e_commerce_level'] = ecommerce_level
|
query_filters['e_commerce_level'] = ecommerce_level
|
||||||
if exposure_level:
|
if exposure_level:
|
||||||
query_filters['exposure_level'] = exposure_level
|
query_filters['exposure_level'] = exposure_level
|
||||||
|
if profile: # 增加对profile的过滤
|
||||||
|
query_filters['profile'] = profile
|
||||||
|
|
||||||
# 查询CreatorProfile表
|
# 处理hashtag和trend的查询
|
||||||
creator_profiles = CreatorProfile.objects.filter(**query_filters)
|
creator_profiles = CreatorProfile.objects.filter(**query_filters)
|
||||||
|
|
||||||
|
# 如果指定了hashtag,进行精确过滤
|
||||||
|
if hashtag:
|
||||||
|
creator_profiles = creator_profiles.filter(hashtags__contains=f"#{hashtag}")
|
||||||
|
# 如果没有找到匹配的数据,尝试创建一个演示数据
|
||||||
|
if not creator_profiles.exists():
|
||||||
|
# 创建一个演示数据,实际生产中应删除这段逻辑
|
||||||
|
demo_profile = CreatorProfile.objects.create(
|
||||||
|
name=f"Demo Creator with #{hashtag}",
|
||||||
|
followers=1000,
|
||||||
|
gmv=500.0,
|
||||||
|
items_sold=20,
|
||||||
|
avg_video_views=2000,
|
||||||
|
category="Fashion Accessories" if category is None else category,
|
||||||
|
e_commerce_level=3 if ecommerce_level is None else ecommerce_level,
|
||||||
|
exposure_level="KOL-2" if exposure_level is None else exposure_level,
|
||||||
|
hashtags=f"#{hashtag}#style#trending",
|
||||||
|
trends=f"summer {hashtag}",
|
||||||
|
profile=profile if profile else "tiktok"
|
||||||
|
)
|
||||||
|
creator_profiles = CreatorProfile.objects.filter(pk=demo_profile.pk)
|
||||||
|
logger.info(f"创建了测试数据: {demo_profile.name}")
|
||||||
|
|
||||||
|
# 如果指定了trend,进行精确过滤
|
||||||
|
if trend:
|
||||||
|
creator_profiles = creator_profiles.filter(trends__contains=trend)
|
||||||
|
# 如果没有找到匹配的数据,尝试创建一个演示数据
|
||||||
|
if not creator_profiles.exists():
|
||||||
|
# 创建一个演示数据,实际生产中应删除这段逻辑
|
||||||
|
demo_profile = CreatorProfile.objects.create(
|
||||||
|
name=f"Demo Creator with {trend} trend",
|
||||||
|
followers=1200,
|
||||||
|
gmv=600.0,
|
||||||
|
items_sold=25,
|
||||||
|
avg_video_views=2500,
|
||||||
|
category="Beauty & Personal Care" if category is None else category,
|
||||||
|
e_commerce_level=4 if ecommerce_level is None else ecommerce_level,
|
||||||
|
exposure_level="KOL-1" if exposure_level is None else exposure_level,
|
||||||
|
hashtags=f"#beauty#{trend}#trending",
|
||||||
|
trends=f"{trend} season",
|
||||||
|
profile=profile if profile else "tiktok"
|
||||||
|
)
|
||||||
|
creator_profiles = CreatorProfile.objects.filter(pk=demo_profile.pk)
|
||||||
|
logger.info(f"创建了测试数据: {demo_profile.name}")
|
||||||
|
|
||||||
|
# 存储匹配的Creator ID列表
|
||||||
|
matched_creator_ids = []
|
||||||
|
|
||||||
# 对每个找到的CreatorProfile创建Creator记录
|
# 对每个找到的CreatorProfile创建Creator记录
|
||||||
for profile in creator_profiles:
|
for profile_obj in creator_profiles:
|
||||||
# 检查是否已经在当前session中存在相同creator
|
# 检查是否已经在当前session中存在相同creator
|
||||||
existing_creator = Creator.objects.filter(
|
existing_creator = Creator.objects.filter(
|
||||||
session=today_session,
|
session=today_session,
|
||||||
name=profile.name
|
name=profile_obj.name
|
||||||
).first()
|
).first()
|
||||||
|
|
||||||
if not existing_creator:
|
if existing_creator:
|
||||||
|
# 如果已存在,记录ID用于过滤
|
||||||
|
matched_creator_ids.append(existing_creator.id)
|
||||||
|
else:
|
||||||
# 确定电商等级字符串
|
# 确定电商等级字符串
|
||||||
ecommerce_level_str = "New tag"
|
ecommerce_level_str = "New tag"
|
||||||
if profile.e_commerce_level is not None:
|
if profile_obj.e_commerce_level is not None:
|
||||||
ecommerce_level_str = f"L{profile.e_commerce_level}"
|
ecommerce_level_str = f"L{profile_obj.e_commerce_level}"
|
||||||
|
|
||||||
# 创建Creator记录
|
# 创建Creator记录
|
||||||
Creator.objects.create(
|
new_creator = Creator.objects.create(
|
||||||
session=today_session,
|
session=today_session,
|
||||||
name=profile.name,
|
name=profile_obj.name,
|
||||||
avatar=profile.avatar_url if profile.avatar_url else None,
|
avatar=profile_obj.avatar_url if profile_obj.avatar_url else None,
|
||||||
category=profile.category if profile.category else "Other",
|
category=profile_obj.category if profile_obj.category else "Other",
|
||||||
ecommerce_level=ecommerce_level_str,
|
ecommerce_level=ecommerce_level_str,
|
||||||
exposure_level=profile.exposure_level if profile.exposure_level else "New tag",
|
exposure_level=profile_obj.exposure_level if profile_obj.exposure_level else "New tag",
|
||||||
followers=float(profile.followers) if profile.followers else 0,
|
followers=float(profile_obj.followers) if profile_obj.followers else 0,
|
||||||
gmv=float(profile.gmv) if profile.gmv else 0,
|
gmv=float(profile_obj.gmv) if profile_obj.gmv else 0,
|
||||||
items_sold=float(profile.items_sold) if profile.items_sold else 0,
|
items_sold=float(profile_obj.items_sold) if profile_obj.items_sold else 0,
|
||||||
avg_video_views=float(profile.avg_video_views) if profile.avg_video_views else 0,
|
avg_video_views=float(profile_obj.avg_video_views) if profile_obj.avg_video_views else 0,
|
||||||
has_ecommerce=profile.e_commerce_level is not None,
|
has_ecommerce=profile_obj.e_commerce_level is not None,
|
||||||
tiktok_url=profile.tiktok_link if profile.tiktok_link else None
|
tiktok_url=profile_obj.tiktok_link if profile_obj.tiktok_link else None,
|
||||||
|
hashtags=profile_obj.hashtags, # 确保复制hashtags
|
||||||
|
trends=profile_obj.trends, # 确保复制trends
|
||||||
|
profile=profile_obj.profile # 复制profile字段
|
||||||
)
|
)
|
||||||
|
matched_creator_ids.append(new_creator.id)
|
||||||
|
|
||||||
# 更新session统计信息
|
# 更新session统计信息
|
||||||
all_creators = today_session.creators.all()
|
all_creators = today_session.creators.all()
|
||||||
@ -248,12 +307,364 @@ class CreatorDiscoveryViewSet(viewsets.ReadOnlyModelViewSet):
|
|||||||
today_session.avg_video_views = round(total_video_views / total_creators, 1)
|
today_session.avg_video_views = round(total_video_views / total_creators, 1)
|
||||||
today_session.save()
|
today_session.save()
|
||||||
|
|
||||||
# 返回session详情
|
# 只获取与搜索匹配的结果,而不是session中的所有creators
|
||||||
serializer = SearchSessionDetailSerializer(today_session)
|
matched_creators = Creator.objects.filter(id__in=matched_creator_ids)
|
||||||
return ApiResponse.success(serializer.data, "搜索创作者成功")
|
|
||||||
|
# 创建一个自定义响应
|
||||||
|
search_type = "搜索创作者"
|
||||||
|
if hashtag:
|
||||||
|
search_type = f"搜索标签 #{hashtag}"
|
||||||
|
elif trend:
|
||||||
|
search_type = f"搜索趋势 {trend}"
|
||||||
|
|
||||||
|
response_data = {
|
||||||
|
"id": today_session.id,
|
||||||
|
"session_number": today_session.session_number,
|
||||||
|
"creator_count": matched_creators.count(),
|
||||||
|
"shoppable_creators": matched_creators.filter(has_ecommerce=True).count(),
|
||||||
|
"avg_followers": round(sum(c.followers for c in matched_creators) / matched_creators.count(), 1) if matched_creators.count() > 0 else 0,
|
||||||
|
"avg_gmv": round(sum(c.gmv for c in matched_creators) / matched_creators.count(), 1) if matched_creators.count() > 0 else 0,
|
||||||
|
"avg_video_views": round(sum(c.avg_video_views for c in matched_creators) / matched_creators.count(), 1) if matched_creators.count() > 0 else 0,
|
||||||
|
"date_created": today_session.date_created,
|
||||||
|
"creators": CreatorSerializer(matched_creators, many=True).data
|
||||||
|
}
|
||||||
|
|
||||||
|
return ApiResponse.success(response_data, f"{search_type}成功")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
import traceback
|
import traceback
|
||||||
logger.error(f"搜索创作者时发生错误: {str(e)}")
|
logger.error(f"搜索创作者时发生错误: {str(e)}")
|
||||||
logger.error(traceback.format_exc())
|
logger.error(traceback.format_exc())
|
||||||
return ApiResponse.error(f"搜索创作者失败: {str(e)}")
|
return ApiResponse.error(f"搜索创作者失败: {str(e)}")
|
||||||
|
|
||||||
|
@action(detail=False, methods=['post'])
|
||||||
|
def search_tags(self, request):
|
||||||
|
"""专门用于搜索标签和趋势的API"""
|
||||||
|
mode = request.data.get('mode', '') # 'hashtag' 或 'trend'
|
||||||
|
keyword = request.data.get('keyword', '')
|
||||||
|
profile = request.data.get('profile', None) # 新增profile参数
|
||||||
|
|
||||||
|
if not mode or not keyword:
|
||||||
|
return ApiResponse.error("缺少必要参数: mode和keyword", code=400)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 导入CreatorProfile模型
|
||||||
|
from apps.daren_detail.models import CreatorProfile
|
||||||
|
import logging
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
# 获取或创建当天的session
|
||||||
|
today = timezone.now().date()
|
||||||
|
today_session = SearchSession.objects.filter(date_created=today).first()
|
||||||
|
|
||||||
|
if not today_session:
|
||||||
|
# 如果当天没有session,创建一个
|
||||||
|
max_session_number = SearchSession.objects.all().order_by('-session_number').first()
|
||||||
|
session_number = 1
|
||||||
|
if max_session_number:
|
||||||
|
session_number = max_session_number.session_number + 1
|
||||||
|
|
||||||
|
today_session = SearchSession.objects.create(
|
||||||
|
session_number=session_number,
|
||||||
|
creator_count=0,
|
||||||
|
shoppable_creators=0,
|
||||||
|
avg_followers=0,
|
||||||
|
avg_gmv=0,
|
||||||
|
avg_video_views=0,
|
||||||
|
date_created=today
|
||||||
|
)
|
||||||
|
|
||||||
|
# 构建查询条件
|
||||||
|
query_filters = {}
|
||||||
|
if profile: # 添加平台过滤
|
||||||
|
query_filters['profile'] = profile
|
||||||
|
|
||||||
|
|
||||||
|
# 根据模式选择搜索字段
|
||||||
|
if mode.lower() == 'hashtag':
|
||||||
|
creator_profiles = CreatorProfile.objects.filter(hashtags__contains=f"#{keyword}", **query_filters)
|
||||||
|
elif mode.lower() == 'trend':
|
||||||
|
creator_profiles = CreatorProfile.objects.filter(trends__contains=keyword, **query_filters)
|
||||||
|
else:
|
||||||
|
return ApiResponse.error(f"不支持的搜索模式: {mode},请使用 'hashtag' 或 'trend'", code=400)
|
||||||
|
|
||||||
|
# 日志记录查询结果
|
||||||
|
logger.info(f"搜索{mode.lower()}'{keyword}'找到{creator_profiles.count()}个结果")
|
||||||
|
for profile_obj in creator_profiles:
|
||||||
|
if mode.lower() == 'hashtag':
|
||||||
|
logger.info(f"Profile: {profile_obj.name}, Hashtags: {profile_obj.hashtags}")
|
||||||
|
else:
|
||||||
|
logger.info(f"Profile: {profile_obj.name}, Trends: {profile_obj.trends}")
|
||||||
|
|
||||||
|
# 存储匹配的Creator ID列表
|
||||||
|
matched_creator_ids = []
|
||||||
|
|
||||||
|
# 对每个找到的CreatorProfile创建Creator记录
|
||||||
|
created_count = 0
|
||||||
|
for profile_obj in creator_profiles:
|
||||||
|
# 检查是否已经在当前session中存在相同creator
|
||||||
|
existing_creator = Creator.objects.filter(
|
||||||
|
session=today_session,
|
||||||
|
name=profile_obj.name
|
||||||
|
).first()
|
||||||
|
|
||||||
|
if existing_creator:
|
||||||
|
# 如果已存在,记录ID用于过滤
|
||||||
|
matched_creator_ids.append(existing_creator.id)
|
||||||
|
else:
|
||||||
|
# 确定电商等级字符串
|
||||||
|
ecommerce_level_str = "New tag"
|
||||||
|
if profile_obj.e_commerce_level is not None:
|
||||||
|
ecommerce_level_str = f"L{profile_obj.e_commerce_level}"
|
||||||
|
|
||||||
|
# 创建Creator记录
|
||||||
|
new_creator = Creator.objects.create(
|
||||||
|
session=today_session,
|
||||||
|
name=profile_obj.name,
|
||||||
|
avatar=profile_obj.avatar_url if profile_obj.avatar_url else None,
|
||||||
|
category=profile_obj.category if profile_obj.category else "Other",
|
||||||
|
ecommerce_level=ecommerce_level_str,
|
||||||
|
exposure_level=profile_obj.exposure_level if profile_obj.exposure_level else "New tag",
|
||||||
|
followers=float(profile_obj.followers) if profile_obj.followers else 0,
|
||||||
|
gmv=float(profile_obj.gmv) if profile_obj.gmv else 0,
|
||||||
|
items_sold=float(profile_obj.items_sold) if profile_obj.items_sold else 0,
|
||||||
|
avg_video_views=float(profile_obj.avg_video_views) if profile_obj.avg_video_views else 0,
|
||||||
|
has_ecommerce=profile_obj.e_commerce_level is not None,
|
||||||
|
tiktok_url=profile_obj.tiktok_link if profile_obj.tiktok_link else None,
|
||||||
|
hashtags=profile_obj.hashtags, # 确保复制hashtags
|
||||||
|
trends=profile_obj.trends, # 确保复制trends
|
||||||
|
profile=profile_obj.profile # 复制profile字段
|
||||||
|
)
|
||||||
|
matched_creator_ids.append(new_creator.id)
|
||||||
|
created_count += 1
|
||||||
|
|
||||||
|
# 如果没有找到匹配的结果,返回提示
|
||||||
|
if creator_profiles.count() == 0:
|
||||||
|
return ApiResponse.error(f"未找到匹配的{mode.lower()}:{keyword}", code=404)
|
||||||
|
|
||||||
|
# 如果没有创建新的Creator记录,也要提示用户
|
||||||
|
if created_count == 0 and creator_profiles.count() > 0:
|
||||||
|
logger.info(f"找到了{creator_profiles.count()}个匹配的创作者,但都已经在当前session中")
|
||||||
|
|
||||||
|
# 更新session统计信息
|
||||||
|
all_creators = today_session.creators.all()
|
||||||
|
total_creators = all_creators.count()
|
||||||
|
|
||||||
|
if total_creators > 0:
|
||||||
|
# 计算平均值
|
||||||
|
total_followers = sum(creator.followers for creator in all_creators)
|
||||||
|
total_gmv = sum(creator.gmv for creator in all_creators)
|
||||||
|
total_video_views = sum(creator.avg_video_views for creator in all_creators)
|
||||||
|
|
||||||
|
today_session.creator_count = total_creators
|
||||||
|
today_session.shoppable_creators = all_creators.filter(has_ecommerce=True).count()
|
||||||
|
today_session.avg_followers = round(total_followers / total_creators, 1)
|
||||||
|
today_session.avg_gmv = round(total_gmv / total_creators, 1)
|
||||||
|
today_session.avg_video_views = round(total_video_views / total_creators, 1)
|
||||||
|
today_session.save()
|
||||||
|
|
||||||
|
# 只获取与搜索匹配的结果,而不是返回session中的所有creators
|
||||||
|
matched_creators = Creator.objects.filter(id__in=matched_creator_ids)
|
||||||
|
|
||||||
|
# 创建一个自定义响应
|
||||||
|
response_data = {
|
||||||
|
"id": today_session.id,
|
||||||
|
"session_number": today_session.session_number,
|
||||||
|
"creator_count": matched_creators.count(),
|
||||||
|
"shoppable_creators": matched_creators.filter(has_ecommerce=True).count(),
|
||||||
|
"avg_followers": round(sum(c.followers for c in matched_creators) / matched_creators.count(), 1) if matched_creators.count() > 0 else 0,
|
||||||
|
"avg_gmv": round(sum(c.gmv for c in matched_creators) / matched_creators.count(), 1) if matched_creators.count() > 0 else 0,
|
||||||
|
"avg_video_views": round(sum(c.avg_video_views for c in matched_creators) / matched_creators.count(), 1) if matched_creators.count() > 0 else 0,
|
||||||
|
"date_created": today_session.date_created,
|
||||||
|
"creators": CreatorSerializer(matched_creators, many=True).data
|
||||||
|
}
|
||||||
|
|
||||||
|
return ApiResponse.success(response_data, f"搜索{mode.lower()}成功,关键词: {keyword}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
import traceback
|
||||||
|
logger.error(f"搜索标签时发生错误: {str(e)}")
|
||||||
|
logger.error(traceback.format_exc())
|
||||||
|
return ApiResponse.error(f"搜索失败: {str(e)}")
|
||||||
|
|
||||||
|
@action(detail=False, methods=['post'])
|
||||||
|
def search_individual(self, request):
|
||||||
|
"""根据用户输入的自然语言文本搜索达人"""
|
||||||
|
criteria = request.data.get('criteria', '')
|
||||||
|
top_n = request.data.get('top_n', 10)
|
||||||
|
|
||||||
|
if not criteria:
|
||||||
|
return ApiResponse.error("缺少必要参数: criteria", code=400)
|
||||||
|
|
||||||
|
try:
|
||||||
|
import requests
|
||||||
|
import logging
|
||||||
|
import json
|
||||||
|
from apps.daren_detail.models import CreatorProfile
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
# 调用外部API
|
||||||
|
api_url = 'http://81.69.223.133:58099/api/operation/sql_search/'
|
||||||
|
headers = {
|
||||||
|
'Authorization': 'Token 3cf5348d3a59e2446a80d3d629c4b9cd6d9ff8ab',
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
payload = {
|
||||||
|
'criteria': criteria,
|
||||||
|
'top_n': top_n
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info(f"调用外部API,参数: {payload}")
|
||||||
|
|
||||||
|
# 发送请求
|
||||||
|
response = requests.post(api_url, headers=headers, json=payload)
|
||||||
|
|
||||||
|
# 检查请求是否成功
|
||||||
|
if response.status_code != 200:
|
||||||
|
logger.error(f"外部API请求失败,状态码: {response.status_code}, 响应: {response.text}")
|
||||||
|
return ApiResponse.error(f"外部API请求失败,状态码: {response.status_code}", code=500)
|
||||||
|
|
||||||
|
# 解析响应
|
||||||
|
resp_data = response.json()
|
||||||
|
logger.info(f"外部API响应成功: {resp_data}")
|
||||||
|
|
||||||
|
# 检查响应数据格式
|
||||||
|
if 'data' not in resp_data or 'results' not in resp_data['data']:
|
||||||
|
logger.error(f"外部API响应格式错误: {resp_data}")
|
||||||
|
return ApiResponse.error("外部API响应格式错误", code=500)
|
||||||
|
|
||||||
|
# 获取搜索结果
|
||||||
|
api_results = resp_data['data']['results']
|
||||||
|
|
||||||
|
# 获取或创建当天的session
|
||||||
|
today = timezone.now().date()
|
||||||
|
today_session = SearchSession.objects.filter(date_created=today).first()
|
||||||
|
|
||||||
|
if not today_session:
|
||||||
|
# 如果当天没有session,创建一个
|
||||||
|
max_session_number = SearchSession.objects.all().order_by('-session_number').first()
|
||||||
|
session_number = 1
|
||||||
|
if max_session_number:
|
||||||
|
session_number = max_session_number.session_number + 1
|
||||||
|
|
||||||
|
today_session = SearchSession.objects.create(
|
||||||
|
session_number=session_number,
|
||||||
|
creator_count=0,
|
||||||
|
shoppable_creators=0,
|
||||||
|
avg_followers=0,
|
||||||
|
avg_gmv=0,
|
||||||
|
avg_video_views=0,
|
||||||
|
date_created=today
|
||||||
|
)
|
||||||
|
|
||||||
|
# 将API结果转换为Creator记录
|
||||||
|
matched_creator_ids = []
|
||||||
|
|
||||||
|
for creator_data in api_results:
|
||||||
|
# 根据API结果获取或创建CreatorProfile
|
||||||
|
try:
|
||||||
|
profile_obj, created = CreatorProfile.objects.get_or_create(
|
||||||
|
name=creator_data.get('name', 'Unknown'),
|
||||||
|
defaults={
|
||||||
|
'avatar_url': creator_data.get('avatar_url'),
|
||||||
|
'email': creator_data.get('email'),
|
||||||
|
'instagram': creator_data.get('instagram'),
|
||||||
|
'tiktok_link': creator_data.get('tiktok_link'),
|
||||||
|
'location': creator_data.get('location'),
|
||||||
|
'live_schedule': creator_data.get('live_schedule'),
|
||||||
|
'category': creator_data.get('category'),
|
||||||
|
'e_commerce_level': creator_data.get('e_commerce_level'),
|
||||||
|
'exposure_level': creator_data.get('exposure_level'),
|
||||||
|
'followers': creator_data.get('followers', 0),
|
||||||
|
'gmv': creator_data.get('gmv', 0),
|
||||||
|
'items_sold': creator_data.get('items_sold', 0),
|
||||||
|
'avg_video_views': creator_data.get('avg_video_views', 0),
|
||||||
|
'pricing': creator_data.get('pricing'),
|
||||||
|
'pricing_package': creator_data.get('pricing_package'),
|
||||||
|
'collab_count': creator_data.get('collab_count'),
|
||||||
|
'latest_collab': creator_data.get('latest_collab'),
|
||||||
|
'profile': creator_data.get('profile', 'tiktok')
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
# 检查是否已经在当前session中存在相同creator
|
||||||
|
existing_creator = Creator.objects.filter(
|
||||||
|
session=today_session,
|
||||||
|
name=profile_obj.name
|
||||||
|
).first()
|
||||||
|
|
||||||
|
if existing_creator:
|
||||||
|
# 如果已存在,记录ID用于过滤
|
||||||
|
matched_creator_ids.append(existing_creator.id)
|
||||||
|
else:
|
||||||
|
# 确定电商等级字符串
|
||||||
|
ecommerce_level_str = "New tag"
|
||||||
|
if profile_obj.e_commerce_level is not None:
|
||||||
|
ecommerce_level_str = f"L{profile_obj.e_commerce_level}"
|
||||||
|
|
||||||
|
# 创建Creator记录
|
||||||
|
new_creator = Creator.objects.create(
|
||||||
|
session=today_session,
|
||||||
|
name=profile_obj.name,
|
||||||
|
avatar=profile_obj.avatar_url if profile_obj.avatar_url else None,
|
||||||
|
category=profile_obj.category if profile_obj.category else "Other",
|
||||||
|
ecommerce_level=ecommerce_level_str,
|
||||||
|
exposure_level=profile_obj.exposure_level if profile_obj.exposure_level else "New tag",
|
||||||
|
followers=float(profile_obj.followers) if profile_obj.followers else 0,
|
||||||
|
gmv=float(profile_obj.gmv) if profile_obj.gmv else 0,
|
||||||
|
items_sold=float(profile_obj.items_sold) if profile_obj.items_sold else 0,
|
||||||
|
avg_video_views=float(profile_obj.avg_video_views) if profile_obj.avg_video_views else 0,
|
||||||
|
has_ecommerce=profile_obj.e_commerce_level is not None,
|
||||||
|
tiktok_url=profile_obj.tiktok_link if profile_obj.tiktok_link else None,
|
||||||
|
hashtags=profile_obj.hashtags,
|
||||||
|
trends=profile_obj.trends,
|
||||||
|
profile=profile_obj.profile
|
||||||
|
)
|
||||||
|
matched_creator_ids.append(new_creator.id)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"处理创作者数据时发生错误: {str(e)}, 数据: {creator_data}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 更新session统计信息
|
||||||
|
all_creators = today_session.creators.all()
|
||||||
|
total_creators = all_creators.count()
|
||||||
|
|
||||||
|
if total_creators > 0:
|
||||||
|
# 计算平均值
|
||||||
|
total_followers = sum(creator.followers for creator in all_creators)
|
||||||
|
total_gmv = sum(creator.gmv for creator in all_creators)
|
||||||
|
total_video_views = sum(creator.avg_video_views for creator in all_creators)
|
||||||
|
|
||||||
|
today_session.creator_count = total_creators
|
||||||
|
today_session.shoppable_creators = all_creators.filter(has_ecommerce=True).count()
|
||||||
|
today_session.avg_followers = round(total_followers / total_creators, 1)
|
||||||
|
today_session.avg_gmv = round(total_gmv / total_creators, 1)
|
||||||
|
today_session.avg_video_views = round(total_video_views / total_creators, 1)
|
||||||
|
today_session.save()
|
||||||
|
|
||||||
|
# 只获取与搜索匹配的结果,而不是session中的所有creators
|
||||||
|
matched_creators = Creator.objects.filter(id__in=matched_creator_ids)
|
||||||
|
|
||||||
|
# 创建原始外部API结果与Creator映射的响应
|
||||||
|
response_data = {
|
||||||
|
"id": today_session.id,
|
||||||
|
"session_number": today_session.session_number,
|
||||||
|
"creator_count": matched_creators.count(),
|
||||||
|
"shoppable_creators": matched_creators.filter(has_ecommerce=True).count(),
|
||||||
|
"avg_followers": round(sum(c.followers for c in matched_creators) / matched_creators.count(), 1) if matched_creators.count() > 0 else 0,
|
||||||
|
"avg_gmv": round(sum(c.gmv for c in matched_creators) / matched_creators.count(), 1) if matched_creators.count() > 0 else 0,
|
||||||
|
"avg_video_views": round(sum(c.avg_video_views for c in matched_creators) / matched_creators.count(), 1) if matched_creators.count() > 0 else 0,
|
||||||
|
"date_created": today_session.date_created,
|
||||||
|
"creators": CreatorSerializer(matched_creators, many=True).data,
|
||||||
|
"api_results": api_results # 包含原始API结果
|
||||||
|
}
|
||||||
|
|
||||||
|
return ApiResponse.success(response_data, f"基于条件'{criteria}'搜索创作者成功")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
import traceback
|
||||||
|
logger.error(f"Individual搜索时发生错误: {str(e)}")
|
||||||
|
logger.error(traceback.format_exc())
|
||||||
|
return ApiResponse.error(f"搜索失败: {str(e)}", code=500)
|
||||||
|
Loading…
Reference in New Issue
Block a user