| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146 |
- # 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
- def get_url_signature(url: str, platform_id: str = "") -> str:
- """
- 获取 URL 的签名(用于快速比较)
- 基于标准化 URL 生成签名,可用于:
- - 快速判断两个 URL 是否指向同一内容
- - 作为缓存键
- Args:
- url: 原始 URL
- platform_id: 平台 ID
- Returns:
- URL 签名字符串
- """
- return normalize_url(url, platform_id)
|