# coding=utf-8 """ URL 处理工具模块 提供 URL 标准化功能,用于去重时消除动态参数的影响: - normalize_url: 标准化 URL,去除动态参数 """ from urllib.parse import urlparse, urlunparse, parse_qs, urlencode from typing import Dict, Set # 各平台需要移除的特定参数 # - weibo: 有 band_rank(排名)和 Refer(来源)动态参数 # - 其他平台: URL 为路径格式或简单关键词查询,无需处理 PLATFORM_PARAMS_TO_REMOVE: Dict[str, Set[str]] = { # 微博:band_rank 是动态排名参数,Refer 是来源参数,t 是时间范围参数 # 示例:https://s.weibo.com/weibo?q=xxx&t=31&band_rank=1&Refer=top # 保留:q(关键词) # 移除:band_rank, Refer, t "weibo": {"band_rank", "Refer", "t"}, } # 通用追踪参数(适用于所有平台) # 这些参数通常由分享链接或广告追踪添加,不影响内容识别 COMMON_TRACKING_PARAMS: Set[str] = { # UTM 追踪参数 "utm_source", "utm_medium", "utm_campaign", "utm_term", "utm_content", # 常见追踪参数 "ref", "referrer", "source", "channel", # 时间戳和随机参数 "_t", "timestamp", "_", "random", # 分享相关 "share_token", "share_id", "share_from", } def normalize_url(url: str, platform_id: str = "") -> str: """ 标准化 URL,去除动态参数 用于数据库去重,确保同一条新闻的不同 URL 变体能被正确识别为同一条。 处理规则: 1. 去除平台特定的动态参数(如微博的 band_rank) 2. 去除通用追踪参数(如 utm_*) 3. 保留核心查询参数(如搜索关键词 q=, wd=, keyword=) 4. 对查询参数按字母序排序(确保一致性) Args: url: 原始 URL platform_id: 平台 ID,用于应用平台特定规则 Returns: 标准化后的 URL Examples: >>> normalize_url("https://s.weibo.com/weibo?q=test&band_rank=6&Refer=top", "weibo") 'https://s.weibo.com/weibo?q=test' >>> normalize_url("https://example.com/page?id=1&utm_source=twitter", "") 'https://example.com/page?id=1' """ if not url: return url try: # 解析 URL parsed = urlparse(url) # 如果没有查询参数,直接返回 if not parsed.query: return url # 解析查询参数 params = parse_qs(parsed.query, keep_blank_values=True) # 收集需要移除的参数(使用小写进行比较) params_to_remove: Set[str] = set() # 添加通用追踪参数 params_to_remove.update(COMMON_TRACKING_PARAMS) # 添加平台特定参数 if platform_id and platform_id in PLATFORM_PARAMS_TO_REMOVE: params_to_remove.update(PLATFORM_PARAMS_TO_REMOVE[platform_id]) # 过滤参数(参数名转小写进行比较) filtered_params = { key: values for key, values in params.items() if key.lower() not in {p.lower() for p in params_to_remove} } # 如果过滤后没有参数了,返回不带查询字符串的 URL if not filtered_params: return urlunparse(( parsed.scheme, parsed.netloc, parsed.path, parsed.params, "", # 空查询字符串 "" # 移除 fragment )) # 重建查询字符串(按字母序排序以确保一致性) sorted_params = [] for key in sorted(filtered_params.keys()): for value in filtered_params[key]: sorted_params.append((key, value)) new_query = urlencode(sorted_params) # 重建 URL(移除 fragment) normalized = urlunparse(( parsed.scheme, parsed.netloc, parsed.path, parsed.params, new_query, "" # 移除 fragment )) return normalized except Exception: # 解析失败时返回原始 URL return url