import json import re import random import requests import time import unicodedata from brand_spider.web_dec import decrypt_via_service, get_guid try: from config import proxy_url as CONFIG_PROXY_URL, proxy_mode as CONFIG_PROXY_MODE except ImportError: CONFIG_PROXY_URL = None CONFIG_PROXY_MODE = 1 # Forbidden 后 3 分钟内统一使用代理:记录代理生效截止时间与当前代理 _FORBIDDEN_PROXY_UNTIL = 0.0 _FORBIDDEN_PROXY_DICT = None _FORBIDDEN_PROXY_MINUTES = 0.5 special_char_pattern = re.compile(r'[^\w\s]') def remove_accents(input_str): # 将字符分解为基础字符和重音符号 nksel = unicodedata.normalize('NFKD', input_str) # 过滤掉非间距重音符号,并重新编码 return "".join([c for c in nksel if not unicodedata.combining(c)]) def clean_text(text): text = remove_accents(text) return special_char_pattern.sub(' ', text).replace(" ","") def create_code_generator(): counter = random.randint(0, 0xFFFFFF) def get_code(): nonlocal counter counter = (counter + 1) % 0xFFFFFF low_16_bits = counter & 0xFFFF return f"{low_16_bits:04x}" return get_code() def search(hashsearch, data, proxies=None): global _FORBIDDEN_PROXY_UNTIL, _FORBIDDEN_PROXY_DICT url = "https://api.branddb.wipo.int/search" headers = { "accept": "application/json, text/plain, */*", "accept-language": "zh-CN,zh;q=0.9,en;q=0.8", "cache-control": "no-cache", "content-type": "application/json", "hashsearch": hashsearch, "origin": "https://branddb.wipo.int", "pragma": "no-cache", "priority": "u=1, i", "referer": "https://branddb.wipo.int/", "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36" } # 若 3 分钟内曾出现 Forbidden,则直接使用当时保存的代理 now = time.time() if now < _FORBIDDEN_PROXY_UNTIL and _FORBIDDEN_PROXY_DICT: proxies = _FORBIDDEN_PROXY_DICT print("处于 Forbidden 代理窗口内,直接使用代理",proxies) # 发送 POST 请求 response = requests.post(url, headers=headers, json=data, proxies=proxies) enc_text = response.text # print("原始结果", enc_text) # 若返回 Forbidden,则从 config 的 proxy_url 获取代理 IP 后重试,并开启 3 分钟代理窗口 if (enc_text.strip() == '{"message":"Forbidden"}' or "Too Many Requests" in enc_text) and CONFIG_PROXY_URL: try: proxy_resp = requests.get(CONFIG_PROXY_URL, timeout=10) print("代理请求结果->:", proxy_resp.text) # 模式 2:账号密码代理,接口返回 JSON if CONFIG_PROXY_MODE == 2: resp_json = proxy_resp.json() proxy_list = resp_json.get("data", {}).get("list") or [] first_item = proxy_list[0] if proxy_list else None if first_item: ip = first_item.get("ip") port = first_item.get("port") account = first_item.get("account") password = first_item.get("password") if ip and port and account and password: auth_proxy = f"{account}:{password}@{ip}:{port}" print("获取到账号密码代理:", auth_proxy) proxies = { "http": f"http://{auth_proxy}", "https": f"http://{auth_proxy}", } # 默认模式 1:普通 IP:port 文本 if CONFIG_PROXY_MODE != 2: proxy_ip = (proxy_resp.text or "").strip() if proxy_ip: proxies = { "http": f"http://{proxy_ip}", "https": f"http://{proxy_ip}", } if proxies: response = requests.post(url, headers=headers, json=data, proxies=proxies) enc_text = response.text print("代理重试结果", enc_text) # 记录 3 分钟内都使用该代理 _FORBIDDEN_PROXY_UNTIL = now + _FORBIDDEN_PROXY_MINUTES * 60 _FORBIDDEN_PROXY_DICT = proxies except Exception as e: print("获取代理或重试失败:", e) return enc_text class TaskCancelledError(Exception): """用户取消任务时抛出,供上层捕获并中止""" pass def single_file_handle(brand, strategy="Terms"): status_info = { "Ended": "已结束", "Expired": "已过期的", "Pending": "待决", "Registered": "已注册", "RegisteredMadrid": "国际注册有效", "Unknown": "未知" } country_info = { "AB": "ARABPAT", "AD": "安道尔", "AE": "UAE", "AF": "阿富汗", "AFR": "非洲", "AG": "安提瓜和巴布达", "AI": "安圭拉", "AL": "阿尔巴尼亚", "AM": "亚美尼亚", "AN": "荷属安的列斯", "ANT": "南极洲", "AO": "安哥拉", "AP": "非洲地区知识产权组织 ", "AQ": "南极洲", "AR": "阿根廷", "AS": "美属萨摩亚", "ASI": "亚洲", "AT": "奥地利", "AU": "澳大利亚", "AW": "阿鲁巴", "AX": "奥兰群岛", "AZ": "阿塞拜疆", "BA": "波斯尼亚和黑塞哥维纳", "BB": "巴巴多斯", "BD": "孟加拉国", "BE": "比利时", "BF": "布基纳法索", "BG": "保加利亚", "BH": "巴林", "BI": "布隆迪", "BJ": "贝宁", "BL": "圣巴泰勒米", "BM": "百慕大", "BN": "文莱达鲁萨兰国", "BO": "多民族玻利维亚国", "BQ": "博纳尔、圣俄斯塔休斯和萨巴", "BR": "巴西", "BS": "巴哈马", "BT": "不丹", "BV": "布韦岛", "BW": "博茨瓦纳", "BX": "比荷卢知识产权局", "BY": "白俄罗斯", "BZ": "伯利兹", "CA": "加拿大", "CC": "科科斯群岛(基灵群岛)", "CD": "刚果民主共和国", "CF": "中非共和国", "CG": "刚果", "CH": "瑞士", "CI": "科特迪瓦", "CK": "库克群岛", "CL": "智利", "CM": "喀麦隆", "CN": "中国", "CO": "哥伦比亚", "CR": "哥斯达黎加", "CS": "捷克斯洛伐克", "CU": "古巴", "CV": "佛得角", "CW": "库拉索", "CX": "圣诞岛", "CY": "塞浦路斯", "CZ": "捷克共和国", "DD": "德意志民主共和国", "DE": "德国", "DJ": "吉布提", "DK": "丹麦", "DM": "多米尼克", "DO": "多米尼加", "DT": "西德", "DZ": "阿尔及利亚", "EA": "欧亚专利组织", "EC": "厄瓜多尔", "EE": "爱沙尼亚", "EG": "埃及", "EH": "西撒哈拉", "EM": "欧洲联盟", "EP": "欧洲专利局", "ER": "厄立特里亚", "ES": "西班牙", "ET": "埃塞俄比亚", "EUR": "欧洲", "FI": "芬兰", "FJ": "斐济", "FK": "福克兰群岛(马尔维纳斯群岛)", "FM": "密克罗尼西亚联邦", "FO": "法罗群岛", "FR": "法国", "GA": "加蓬", "GB": "英国", "GC": "海湾阿拉伯国家合作委员会专利局", "GD": "格林纳达", "GE": "格鲁吉亚", "GF": "法属圭亚那", "GG": "格恩西岛", "GH": "加纳", "GI": "直布罗陀", "GL": "格陵兰", "GM": "冈比亚", "GN": "几内亚", "GP": "瓜德罗普", "GQ": "赤道几内亚", "GR": "希腊", "GS": "南乔治亚和南桑威奇群岛", "GT": "危地马拉", "GU": "关岛", "GW": "几内亚比绍", "GY": "圭亚那", "HK": "香港", "HM": "赫德岛和麦克唐纳群岛", "HN": "洪都拉斯", "HR": "克罗地亚", "HT": "海地", "HU": "匈牙利", "IB": "世界知识产权组织国际局", "ID": "印度尼西亚", "IE": "爱尔兰", "IL": "以色列", "IM": "马恩岛", "IN": "印度", "INN": "世界卫生组织", "IO": "英属印度洋领地", "IQ": "伊拉克", "IR": "伊朗伊斯兰共和国", "IS": "冰岛", "IT": "意大利", "JE": "泽西岛", "JM": "牙买加", "JO": "约旦", "JP": "日本", "KE": "肯尼亚", "KG": "吉尔吉斯斯坦", "KH": "柬埔寨", "KI": "基里巴斯", "KM": "科摩罗", "KN": "圣基茨和尼维斯", "KP": "朝鲜民主主义人民共和国", "KR": "大韩民国", "KW": "科威特", "KY": "开曼群岛", "KZ": "哈萨克斯坦", "LA": "老挝人民民主共和国", "LB": "黎巴嫩", "LC": "圣卢西亚", "LI": "列支敦士登", "LISBON": "WIPO", "LK": "斯里兰卡", "LP": "LATIPAT", "LR": "利比里亚", "LS": "莱索托", "LT": "立陶宛", "LU": "卢森堡", "LV": "拉脱维亚", "LY": "利比亚", "MA": "摩洛哥", "MC": "摩纳哥", "MD": "摩尔多瓦共和国", "ME": "黑山", "MF": "圣马丁(法国部分)", "MG": "马达加斯加", "MH": "马绍尔群岛", "MK": "北马其顿共和国", "ML": "马里", "MM": "缅甸", "MN": "蒙古", "MO": "澳门", "MP": "北马里亚纳群岛", "MQ": "马提尼克", "MR": "毛里塔尼亚", "MS": "蒙特塞拉特", "MT": "马耳他", "MU": "毛里求斯", "MV": "马尔代夫", "MW": "马拉维", "MX": "墨西哥", "MY": "马来西亚", "MZ": "莫桑比克", "NA": "纳米比亚", "NAM": "北美洲", "NC": "新喀里多尼亚", "NE": "尼日尔", "NF": "诺福克岛", "NG": "尼日利亚", "NI": "尼加拉瓜", "NL": "荷兰", "NO": "挪威", "NP": "尼泊尔", "NR": "瑙鲁", "NU": "纽埃", "NZ": "新西兰", "OA": "非洲知识产权组织", "OCE": "大洋洲", "OM": "阿曼", "PA": "巴拿马", "PE": "秘鲁", "PF": "法属波利尼西亚", "PG": "巴布亚新几内亚", "PH": "菲律宾", "PK": "巴基斯坦", "PL": "波兰", "PM": "圣皮埃尔和密克隆", "PN": "皮特凯恩", "PR": "波多黎各", "PS": "巴勒斯坦", "PT": "葡萄牙", "PW": "帕劳", "PY": "巴拉圭", "QA": "卡塔尔", "QO": "没有ST.3代码的组织", "QZ": "欧洲联盟", "RE": "留尼汪", "RO": "罗马尼亚", "RS": "塞尔维亚", "RU": "俄罗斯联邦", "RW": "卢旺达", "SA": "沙特阿拉伯", "SAM": "南美洲", "SB": "所罗门群岛", "SC": "塞舌尔", "SD": "苏丹", "SE": "瑞典", "SG": "新加坡", "SH": "圣赫勒拿、阿森松和特里斯坦-达库尼亚", "SI": "斯洛文尼亚", "SIXTER": "WIPO", "SJ": "斯瓦尔巴和扬马延", "SK": "斯洛伐克", "SL": "塞拉里昂", "SM": "圣马力诺", "SN": "塞内加尔", "SO": "索马里", "SR": "苏里南", "SS": "南苏丹", "ST": "圣多美和普林西比", "SU": "苏联", "SV": "萨尔瓦多", "SX": "圣马丁(荷兰部分)", "SY": "阿拉伯叙利亚共和国", "SZ": "斯威士兰", "TC": "特克斯和凯科斯群岛", "TD": "乍得", "TF": "法属南部领地", "TG": "多哥", "TH": "泰国", "TJ": "塔吉克斯坦", "TK": "托克劳", "TL": "东帝汶", "TM": "土库曼斯坦", "TN": "突尼斯", "TO": "汤加", "TR": "土耳其", "TT": "特立尼达和多巴哥", "TV": "图瓦卢", "TW": "台湾(中国的省)", "TZ": "坦桑尼亚联合共和国", "UA": "乌克兰", "UG": "乌干达", "UK": "UK", "UM": "美国本土外小岛屿", "US": "USA", "UY": "乌拉圭", "UZ": "乌兹别克斯坦", "VA": "罗马教廷", "VC": "圣文森特和格林纳丁斯", "VE": "委内瑞拉玻利瓦尔共和国", "VG": "英属维尔京群岛", "VI": "美属维尔京群岛", "VN": "越南", "VU": "瓦努阿图", "WF": "瓦利斯和富图纳", "WHO": "世界卫生组织", "WO": "WIPO", "WS": "萨摩亚", "XK": "科索沃共和国", "XN": "北欧专利局", "XX": "国际", "XXX": "跨国和国际局", "YD": "民主也门", "YE": "也门", "YT": "马约特岛", "YU": "塞尔维亚和黑山", "ZA": "南非", "ZM": "赞比亚", "ZW": "津巴布韦" } check_value = [ "瑞典", "芬兰", "丹麦", "挪威", "冰岛", "法国", "爱尔兰", "荷兰", "比利时", "卢森堡", "英国", "摩纳哥", "德国", "波兰", "捷克", "斯洛伐克", "匈牙利", "奥地利", "瑞士", "列支敦士登", "爱沙尼亚", "拉脱维亚", "立陶宛", "俄罗斯", "白俄罗斯", "乌克兰", "摩尔多瓦", "西班牙", "葡萄牙", "意大利", "希腊", "斯洛文尼亚", "克罗地亚", "罗马尼亚", "保加利亚", "塞尔维亚", "阿尔巴尼亚", "黑山", "马耳他", "塞浦路斯", "北马其顿", "梵蒂冈", "圣马力诺", "安道尔", "波黑", "欧洲联盟" ] check_staus = ["已过期的","已结束"] # 5. 初始化不符合品牌的数据列表 faild_data = [] # 初始化查询失败的品牌的数据列表 query_faild_data = [] # Simple 策略下最多翻 3 页(0,30,60),直到命中 554 行判断 max_pages = 3 if strategy == "Simple" else 1 page = 0 matched = False while page < max_pages and not matched: try: as_structure_dict = { "_id": create_code_generator(), "boolean": "AND", "bricks": [ { "_id": create_code_generator(), "key": "brandName", "value": brand, "strategy": strategy } ] } # 构造 asStructure 内部 JSON 对象 as_structure_dict = as_structure_dict # print(as_structure_dict) # 将内部对象转为 JSON 字符串(作为 asStructure 字段的值) as_structure_str = json.dumps(as_structure_dict, ensure_ascii=False) data = { "sort": "score desc", "rows": "30", "asStructure": as_structure_str, "fg": "_void_" } # 翻页:第二页 start=30,第三页 start=60(仅 Simple 策略) if strategy == "Simple" and page > 0: data["start"] = str(30 * page) # print("请求参数", as_structure_dict, "页码:", page) hashsearch = get_guid() enc_text = search(hashsearch, data) print(f"【{hashsearch}】待解密-->", enc_text) dec_text = decrypt_via_service(hashsearch, enc_text) # print(type(dec_text)) dec_text = str(dec_text) # print(f"解密之后的结果-->", dec_text) data = json.loads(dec_text) except Exception as e: # 仅第一页请求失败时记录为查询失败品牌 if page == 0: import traceback traceback.print_exc() safe_brand = brand.encode('gbk', errors='replace').decode('gbk') print(f"品牌:{safe_brand},处理失败:{e}") # query_faild_data.append({"brand": brand, "time": datetime.datetime.now().strftime("%y-%m-%d %H:%M:%S")}) query_faild_data.append(brand) break # print(data) if data.get("response", {}).get("numFound", 0) > 0: if strategy == "Simple": new_brand_name = clean_text(brand) _brand = new_brand_name.lower() else: _brand = brand docs = data["response"]["docs"] for d in docs: office = d.get("office") status = d.get("status") registrationDate = d.get("registrationDate") status_name = status_info.get(status, status) brand_name = d.get("brandName") if isinstance(brand_name, list): brand_name = "|".join(brand_name) if strategy == "Simple": new_brand_name = clean_text(brand_name) new_brand_name = new_brand_name.lower() if _brand != new_brand_name: continue check_office = d.get("designation") check_office.append(office) for i in set(check_office): office_name = country_info.get(i, i) # print(office_name,office_name in check_value and status_name not in check_staus) if office_name in check_value and status_name not in check_staus: # 命中 554 行判断,记录并结束当前品牌后续翻页 # data_rows = [row for row in data_rows if row.get("品牌") != brand] # print(office_name, office in ["EM","QZ"] and i not in ["EM","QZ"]) if office in ["EM","QZ"] and i not in ["EM","QZ"]: continue if strategy == "Simple": faild_data.append({"brand": brand, "country": office_name, "status": status_name}) # "reason":"|".join(d.get("brandName","") matched = True else: # print("添加",brand,office_name) faild_data.append({"brand": brand, "country": office_name, "status": status_name}) # break if matched: break # 如果 Simple 策略下本页未命中,则翻下一页;其他策略不翻页 if not matched: page += 1 return faild_data,query_faild_data if __name__ == '__main__': file_path = r"D:\\私单交付\\maixiang_AI\\测试图片数据\\品牌采集测试\\brand_task_73\\源文件\\brand_task_75\\源文件\\3.10.xlsx" res = single_file_handle(file_path,"结果.xlsx") print(res)