# -*- coding: UTF-8 -*- ''' @Project :domainScanDemo @File :baidu.py @IDE :PyCharm @Author :梦伴 @Date :2026/3/29 21:54 @explain : 百度域名检测工具 - 包含百度搜索结果查询和百度安全API检测功能 ''' import os import random # 随机数生成模块,用于随机选择浏览器类型 import re # 正则表达式模块,用于从HTML中提取搜索结果 import threading import time from loguru import logger # 日志记录模块,用于记录程序运行日志 from curl_cffi import requests # curl_cffi库,支持浏览器模拟的HTTP请求 from typing import List, Optional, Union, Any # 类型提示 BAIDU_SITE_TIMEOUT_PROXY = max( 0.8, float(os.getenv("DOMAINCHECK_BAIDU_TIMEOUT_PROXY", "1.4") or 1.4), ) BAIDU_SITE_TIMEOUT_DIRECT = max( 1.0, float(os.getenv("DOMAINCHECK_BAIDU_TIMEOUT_DIRECT", "1.8") or 1.8), ) BAIDU_SCAN_TIMEOUT_PROXY = max( 1.0, float(os.getenv("DOMAINCHECK_BAIDU_SCAN_TIMEOUT_PROXY", "1.6") or 1.6), ) BAIDU_SCAN_TIMEOUT_DIRECT = max( 1.2, float(os.getenv("DOMAINCHECK_BAIDU_SCAN_TIMEOUT_DIRECT", "2.0") or 2.0), ) BAIDU_SITE_URL = 'https://m.baidu.com/s' BAIDU_SAFE_SCAN_URL = 'https://mobsec-sec.baidu.com/3.1/scanurl' BAIDU_SITE_PATTERN = r'([^<]+)' _THREAD_LOCAL = threading.local() def _resolve_baidu_timeout(proxies: dict = None, budget_seconds: Optional[float] = None, *, scan: bool = False) -> float: if scan: timeout = BAIDU_SCAN_TIMEOUT_PROXY if proxies else BAIDU_SCAN_TIMEOUT_DIRECT else: timeout = BAIDU_SITE_TIMEOUT_PROXY if proxies else BAIDU_SITE_TIMEOUT_DIRECT timeout = float(timeout or 0.0) if budget_seconds not in (None, "", 0, "0"): timeout = min(timeout, max(0.6, float(budget_seconds or 0.0))) return max(0.6, timeout) BAIDU_SITE_COOKIES = { 'BIDUPSID': '053DBE4D820C6EFB729DC7B13B1F82B2', 'PSTM': '1775657119', 'H_PS_PSSID': '63148_67862_67986_68002_68142_68148_68152_68141_68165_68189_68226_68267_68296_68336_68369_68453_68438_68464_68541_68546_68558_68520_68589_68621_68615_68606_68601_68682_68671_68735_68544_68733_68766_68807_68901_68918_68836_68921_68955_68976_68997_69007_69010_69018_69024_69014', 'BAIDUID': '053DBE4D820C6EFB729DC7B13B1F82B2:FG=1', 'BDORZ': 'B490B5EBF6F3CD402E515D22BCDA1598', 'BAIDUID_BFESS': '053DBE4D820C6EFB729DC7B13B1F82B2:FG=1', 'BA_HECTOR': 'ah0l24210la4050l0g0k0g2g0h20071ktcocj27', 'ZFY': 'Qxyr75Xm7o8zUxYzuFnYoW7cnS:AnVp:BpnrNVkr3usno:C', 'delPer': '0', 'BAIDUID_REF': '053DBE4D820C6EFB729DC7B13B1F82B2:FG=1', 'H_WISE_SIDS': '110085_661771_667681_673676_683089_682564_685373_660925_687556_686285_690306_690303_690478_690576_690334_691545_685595_690883_692754_692777_692118_692910_693221_693364_693403_693656_693941_694124_693392_694171_694236_693996_694316_694324_693886_694409_694379_694696_694780_694783_692006_694884_694918_694996_694991_695032_695118_694840_695140_695193_695110_695262_688610_694933_695278_694865_695214_695374_690651_694577_692378_695452_695457_695480_695603_695642_695631_695668_695824_695843_695866_695888_695892_695897_695975_694177_695900_695939_696145_696150_696153_696069_696078_696066_694359_696288_696308_694985_696297_695715_696128_696313_696457_696431_696473_696492_696590_693385_696610_696682_696679_696662_696651_696655_696658_696673_696643_696110_696729_696733_696772_8000116_8000133_8000138_8000159_8000163_8000167_8000176_8000186_8000190_8000204', 'H_WISE_SIDS_BFESS': '110085_661771_667681_673676_683089_682564_685373_660925_687556_686285_690306_690303_690478_690576_690334_691545_685595_690883_692754_692777_692118_692910_693221_693364_693403_693656_693941_694124_693392_694171_694236_693996_694316_694324_693886_694409_694379_694696_694780_694783_692006_694884_694918_694996_694991_695032_695118_694840_695140_695193_695110_695262_688610_694933_695278_694865_695214_695374_690651_694577_692378_695452_695457_695480_695603_695642_695631_695668_695824_695843_695866_695888_695892_695897_695975_694177_695900_695939_696145_696150_696153_696069_696078_696066_694359_696288_696308_694985_696297_695715_696128_696313_696457_696431_696473_696492_696590_693385_696610_696682_696679_696662_696651_696655_696658_696673_696643_696110_696729_696733_696772_8000116_8000133_8000138_8000159_8000163_8000167_8000176_8000186_8000190_8000204', 'MSA_PBT': '147', 'MSA_ZOOM': '1000', 'wpr': '0', 'COOKIE_SESSION': '0_0_0_0_0_0_0_0_0_0_0_0_0_1775657396%7C1%230_0_0_0_0_0_0_0_1775657396%7C1', 'MSA_PHY_WH': '1440_3440', 'POLYFILL': '0', 'MSA_WH': '1254_940', 'kleck': '7ce2abac229d2e8ba435a8bcc6256f57f53e9d08954443bf', 'PSCBD': '16%3A1%3A3', 'SE_LAUNCH': '5%3A1775657396_16%3A29594323%3A3', 'BDSVRTM': '3', 'PSINO': '6', '__bsi': '17976785662214065461_00_7_R_R_6_0303_c02f_Y', } BAIDU_SITE_HEADERS = { 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7', 'Accept-Language': 'zh-CN,zh;q=0.9', 'Connection': 'keep-alive', 'Sec-Fetch-Dest': 'document', 'Sec-Fetch-Mode': 'navigate', 'Sec-Fetch-Site': 'none', 'Sec-Fetch-User': '?1', 'Upgrade-Insecure-Requests': '1', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36 Edg/146.0.0.0', 'sec-ch-ua': '"Chromium";v="146", "Not-A.Brand";v="24", "Microsoft Edge";v="146"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': '"Windows"', } BAIDU_SAFE_SCAN_HEADERS = { 'Cache-Control': 'no-cache', 'Content-Type': 'application/json; charset=utf-8', 'Host': 'mobsec-sec.baidu.com', 'User-Agent': 'okhttp/3.12.12', } BAIDU_SAFE_SCAN_PARAMS = { 'auth_ver': '2', 'appkey': '4665fd2c6b0922a551e8ae74', 'nonce': '1751309255340', 'lc': '77qHTv4VtmRiXYtd', 'pkg': 'com.baidu.searchbox', 'vc': '-1', 'cuid': 'CF0E6FCCD824D146605C16AC1C8BAC95%7CVFWAO56TC', 'tk': '', 'type': '3', 'vn': '2.6.0', 's': 'fdfef2ce96c1c7193f9b80425020f63e', } def _get_thread_session(): session = getattr(_THREAD_LOCAL, "session", None) if session is None: session = requests.Session() _THREAD_LOCAL.session = session return session def check_site(domain: str, sensitive_words: Optional[List[str]] = None, proxies: dict = None, budget_seconds: Optional[float] = None) -> Union[ None, tuple[bool, str], list[Any]]: ''' 通过百度搜索查询域名的相关信息 使用curl_cffi模拟浏览器访问百度搜索,获取site:domain的搜索结果标题 :param domain: 待查询的域名(如:www.baidu.com) :param proxies: 代理配置(如:{'http': 'http://127.0.0.1:7890', 'https': 'http://127.0.0.1:7890'}) :return: list - 搜索结果标题列表,如果查询失败则返回空列表 ''' # 初始化敏感词列表 if sensitive_words is None: sensitive_words = [] try: # try-except块,用于捕获网络请求中的异常 # 构建搜索查询参数 # wd: 搜索关键词,格式为site:域名 params = { 'wd': f'site:{domain}' # 搜索site:domain,限制搜索结果为指定域名 } session = _get_thread_session() request_started_at = time.perf_counter() logger.info( f"百度site阶段: domain={domain} | stage=request_start | proxy={'yes' if proxies else 'no'}" ) response = session.get( BAIDU_SITE_URL, params=params, headers=BAIDU_SITE_HEADERS, cookies=BAIDU_SITE_COOKIES, timeout=_resolve_baidu_timeout(proxies, budget_seconds=budget_seconds, scan=False), proxies=proxies, ) logger.info( f"百度site阶段: domain={domain} | stage=request_done | status={response.status_code} " f"| elapsed_ms={int((time.perf_counter() - request_started_at) * 1000)}" ) response_text = response.content.decode('utf-8', errors='ignore') # 使用正则表达式提取搜索结果标题 # re.findall()返回所有匹配的结果列表 # 正则解释:匹配 之间的内容 match = re.findall(BAIDU_SITE_PATTERN, response_text, re.S) # 返回匹配结果列表,如果没有匹配则返回空列表 search_results= match if match else [] logger.info( f"百度site阶段: domain={domain} | stage=parsed | result_count={len(search_results)}" ) # 如果没有搜索结果,直接返回通过 if not search_results: return True, '' # 检查每个搜索结果是否包含敏感词 for result in search_results: for sensitive_word in sensitive_words: # 使用正则表达式检查是否包含敏感词 if re.search(sensitive_word, result, re.IGNORECASE): logger.warning(f'检测到敏感词: {sensitive_word} 在结果: {result}') return False, f'检测到敏感词: {sensitive_word} 在结果: {result}' # 所有搜索结果都不包含敏感词,返回通过 return True, '' except Exception as e: # 捕获所有异常 logger.error(f"百度site检测失败: {domain}, 错误: {e}") return False, str(e) def baidu(domain: str, proxies: dict = None, budget_seconds: Optional[float] = None) -> str: ''' 通过百度安全API检测域名安全状态 调用百度移动安全API检测域名是否为风险网站 :param domain: 待检测的域名(如:www.baidu.com) :param proxies: 代理配置(如:{'http': 'http://127.0.0.1:7890', 'https': 'http://127.0.0.1:7890'} :return: str - 安全状态描述("风险网站提示"、"高危网站提示"或"正常"),如果检测失败则返回错误信息 ''' # 构建JSON请求数据 json_data = { 'url': f'https://{domain}/', # 待检测的URL(添加https协议头) } # 提取URL作为后续获取结果的键 key = json_data['url'] try: # try-except块,用于捕获API调用中的异常 # 发送POST请求到百度安全API # url: API接口地址 # params: 查询参数 # headers: HTTP请求头 # json: JSON请求数据 # proxies: 代理配置 # .json(): 解析JSON响应 session = _get_thread_session() request_started_at = time.perf_counter() logger.info( f"百度安全阶段: domain={domain} | stage=request_start | proxy={'yes' if proxies else 'no'}" ) response = session.post( BAIDU_SAFE_SCAN_URL, params=BAIDU_SAFE_SCAN_PARAMS, headers=BAIDU_SAFE_SCAN_HEADERS, json=json_data, proxies=proxies, timeout=_resolve_baidu_timeout(proxies, budget_seconds=budget_seconds, scan=True), ) logger.info( f"百度安全阶段: domain={domain} | stage=request_done | status={response.status_code} " f"| elapsed_ms={int((time.perf_counter() - request_started_at) * 1000)}" ) response = response.json() # # 记录API响应(调试用) # logger.info(response) # 安全状态码映射表 msgs = { 2193: '风险网站提示', # 风险网站 2243: '高危网站提示', # 高危网站 '_': '正常' # 默认正常 } # 从响应中获取安全等级并映射为状态描述 # response['response']['datas'][key].get('grand_level', 'level'): 获取安全等级,默认值为'level' # msgs.get(): 根据安全等级获取对应的状态描述,默认值为'正常' return msgs.get(response['response']['datas'][key].get('grand_level', 'level'), '正常') except Exception as e: # 捕获所有异常 logger.error(f"百度网页安全中心检测失败: {domain}, 错误: {e}") return str(e) # # ========================= 【测试代码】 ========================= # if __name__ == '__main__': # 程序入口,当直接运行此文件时执行以下代码 # # proxies = get_proxies() # proxies = None # # 测试用例1:查询域名搜索结果 # logger.info("=== 测试百度搜索结果查询 ===") # test_domain_1 = '920zl.com' # logger.info(f"查询域名: {test_domain_1}") # # result_1 = check_site(test_domain_1, proxies=proxies) # logger.info(result_1) # # # 测试用例3:检测域名安全状态 # logger.info("=== 测试域名安全检测 ===") # test_domain_3 = 'baidu.com' # logger.info(f"检测域名: {test_domain_3}") # # result_3 = baidu(test_domain_3, proxies=proxies) # logger.info(f"安全状态: {result_3}") # logger.debug(proxies) # domains = ['Lqyingye.com', 'tjjinLikeji.com', 'guokangLxs.com', 'zhuoyijz.com', 'jiandanrongyi.com', 'fromhhc.com', # 'djxow.com', '725game.com', 'gcLpchd.cn', '6rdb.com.cn', 'sunmantech.com', 'hroxa.com', 'guLhu65.cn', # 'xxjdch.com', '7e7fm1.cn', 'kaoxueb.com', 'jabas.cn', 'shzhongyi.com', 'figuangze.com', 'eztw9k.cn', # 'foxok.com', 'oLLbmr.com', 'vwpwwkp.cn', 'cbmrs.com', 'zaozhuang56.com', 'hengxiangracing.com', # 'jdyfbpq.cn', 'czca.cn', 'shibingxiongdi.com', 'ubxbm.cn', 'ydbaoshi.com', '2046hd.cn', 'mffmcp.com', # 'hgLdhzz.com', 'okswmi.cn', 'caidaozg.com', 'dkdywx.cn', 'qkjps.com', 'Lwxgk.cn', 'drtechnoLogy.cn', # 'akcie.cn', '588uu.com', 'kuaiyijian.net', 'c5b.cn', 'faka866.cn', 'wiraf03.cn', 'bobjibar.com', # '166233.cn', 'adLfjcjq.cn', 'whrsom.com'] # for domain in domains: # result_1 = check_site(domain) # logger.info(f"搜索结果标题数: {len(result_1)}") # for i, title in enumerate(result_1, 1): # logger.info(f" 结果{i}: {title}") # time.sleep(1)