fake_utils.py 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. import random
  2. import re
  3. import requests
  4. from datetime import datetime, timedelta
  5. from typing import Dict, Any, Tuple
  6. # ==========================================
  7. # 1. 常量与国家配置区
  8. # ==========================================
  9. DEFAULT_PASSWORD = "Visafly@111"
  10. VISA_TYPE = "Short stay (<90 days) - Tourism"
  11. TRAVEL_PURPOSE = "Tourism / Private visit"
  12. PASSPORT_TYPE = "Ordinary passport"
  13. # 国家专属配置字典,将差异化数据隔离,极大提升可维护性
  14. COUNTRY_CONFIGS = {
  15. "CN": {
  16. "nat_code": None, # randomuser 不支持 CN,置为 None
  17. "pool_name": "tls.cn.sha.fr.sentinel",
  18. "location": "Shanghai",
  19. "province_residence": "Shanghai",
  20. "nationality": "China",
  21. "phone_country_code": "86",
  22. # --- 本地化生成规则(针对 API 不支持的国家) ---
  23. "local_first_names": ["Wei", "Fang", "Jian", "Hui", "Lei", "Ting", "Peng", "Xia", "Bin", "Jie", "San", "Ming"],
  24. "local_last_names": ["Wang", "Li", "Zhang", "Liu", "Chen", "Yang", "Huang", "Zhao", "Wu", "Zhou"],
  25. "phone_prefix": ["138", "139", "150", "151", "180", "189"], # 中国手机号前缀
  26. "phone_length": 11,
  27. # -----------------------------------------------
  28. "default_first_name": "San",
  29. "default_last_name": "Zhang",
  30. "default_phone": "13800000000"
  31. },
  32. "GB": {
  33. "nat_code": "gb", # API 支持 GB,直接依赖 API 生成姓名
  34. "pool_name": "tls.gb.lon.fr.sentinel",
  35. "location": "London",
  36. "province_residence": "London",
  37. "nationality": "United Kingdom",
  38. "phone_country_code": "44",
  39. "default_first_name": "James",
  40. "default_last_name": "Smith",
  41. "default_phone": "7400000000"
  42. }
  43. }
  44. # ==========================================
  45. # 2. 辅助生成函数 (单一职责)
  46. # ==========================================
  47. def _fetch_random_user_data(nat_code: str = None) -> Dict[str, Any]:
  48. """
  49. 请求 randomuser API。
  50. 如果 nat_code 为空,则请求全局随机用户(仅用于借用其随机的出生日期和性别)。
  51. """
  52. url = f"https://randomuser.me/api/?nat={nat_code}" if nat_code else "https://randomuser.me/api/"
  53. try:
  54. resp = requests.get(url, timeout=10)
  55. resp.raise_for_status()
  56. raw = resp.json()
  57. results = raw.get("results")
  58. if results and isinstance(results, list):
  59. return results[0]
  60. except Exception:
  61. pass
  62. return {}
  63. def _generate_localized_name(config: Dict[str, Any], api_user: Dict[str, Any]) -> Tuple[str, str]:
  64. """生成姓名:优先使用本地词库,否则使用 API 返回值"""
  65. if "local_last_names" in config and "local_first_names" in config:
  66. return random.choice(config["local_first_names"]), random.choice(config["local_last_names"])
  67. first = api_user.get("name", {}).get("first") or config["default_first_name"]
  68. last = api_user.get("name", {}).get("last") or config["default_last_name"]
  69. return first, last
  70. def _generate_localized_phone(config: Dict[str, Any], api_user: Dict[str, Any]) -> str:
  71. """生成手机号:优先使用本地规则,否则清洗 API 返回值"""
  72. if "phone_prefix" in config:
  73. prefix = random.choice(config["phone_prefix"])
  74. suffix_len = config.get("phone_length", 11) - len(prefix)
  75. suffix = "".join(str(random.randint(0, 9)) for _ in range(suffix_len))
  76. return f"{prefix}{suffix}"
  77. phone_raw = api_user.get("cell") or api_user.get("phone") or ""
  78. phone = re.sub(r"\D", "", phone_raw)
  79. return phone or config["default_phone"]
  80. def _generate_random_dates() -> Dict[str, str]:
  81. """生成合法的随机日期集合(出行、护照等)"""
  82. today = datetime.today()
  83. base_date = today + timedelta(days=random.randint(20, 90))
  84. # 护照日期(避免闰年 replace 报错,采用天数计算)
  85. start_date = today - timedelta(days=5 * 365)
  86. passport_issue = start_date + timedelta(days=random.randint(0, (today - start_date).days))
  87. passport_expiry = passport_issue + timedelta(days=10 * 365 + 2) - timedelta(days=1)
  88. return {
  89. "departure_origin_date": (base_date - timedelta(days=random.randint(0, 2))).strftime("%Y-%m-%d"),
  90. "arrival_schengen_area_date": base_date.strftime("%Y-%m-%d"),
  91. "departure_schengen_area_date": (base_date + timedelta(days=random.randint(2, 15))).strftime("%Y-%m-%d"),
  92. "passport_issue_date": passport_issue.strftime("%Y-%m-%d"),
  93. "passport_expiry_date": passport_expiry.strftime("%Y-%m-%d"),
  94. }
  95. def _generate_email(first_name: str, last_name: str) -> str:
  96. """基于姓名生成随机邮箱"""
  97. email_prefix = re.sub(r"[^a-z0-9]", "", f"{first_name}{last_name}".lower())
  98. if not email_prefix:
  99. email_prefix = f"user{random.randint(100000, 999999)}"
  100. return f"{email_prefix}{random.randint(1000, 9999)}@gmail-app.com"
  101. # ==========================================
  102. # 3. 主函数
  103. # ==========================================
  104. def generate_random_account_detail(country_code: str = "CN") -> Dict[str, Any]:
  105. """
  106. 基于 randomuser 和指定国家配置生成随机账户信息。
  107. """
  108. config = COUNTRY_CONFIGS.get(country_code.upper())
  109. if not config:
  110. raise ValueError(f"Unsupported country code: {country_code}")
  111. # 1. 抓取 API(即使不支持的国家,也可借用其随机返回的年龄/性别)
  112. api_user = _fetch_random_user_data(config.get("nat_code"))
  113. # 2. 解析基础数据(本地化拦截器)
  114. first_name, last_name = _generate_localized_name(config, api_user)
  115. phone_number = _generate_localized_phone(config, api_user)
  116. gender_raw = str(api_user.get("gender", "")).strip().lower()
  117. gender = "Male" if gender_raw == "male" else "Female"
  118. birthday_raw = api_user.get("dob", {}).get("date", "")
  119. birthday = birthday_raw[:10] if birthday_raw else "1990-01-01"
  120. # 3. 各种衍生计算
  121. email = _generate_email(first_name, last_name)
  122. dates = _generate_random_dates()
  123. app_form_suffix = "".join(str(random.randint(0, 9)) for _ in range(11))
  124. passport_no = "".join(random.choices("ABCDEFGHIJKLMNOPQRSTUVWXYZ", k=2)) + \
  125. "".join(random.choices("0123456789", k=7))
  126. # LO 伦敦 DB 都柏林 PB 北京 SH 上海
  127. # 4. 组装组装并返回
  128. return {
  129. "pool_name": config["pool_name"],
  130. "email": email,
  131. "pwd": DEFAULT_PASSWORD,
  132. "location": config["location"],
  133. "visa_type": VISA_TYPE,
  134. "travel_purpose": TRAVEL_PURPOSE,
  135. "application_form_id": f"FRA1SH{app_form_suffix}",
  136. "last_name": last_name,
  137. "first_name": first_name,
  138. "gender": gender,
  139. "birthday": birthday,
  140. "nationality": config["nationality"],
  141. "province_residence": config["province_residence"],
  142. "passport_type": PASSPORT_TYPE,
  143. "passport_no": passport_no,
  144. "passport_issue_date": dates["passport_issue_date"],
  145. "passport_expiry_date": dates["passport_expiry_date"],
  146. "phone_country_code": config["phone_country_code"],
  147. "phone_number": phone_number,
  148. "departure_origin_date": dates["departure_origin_date"],
  149. "arrival_schengen_area_date": dates["arrival_schengen_area_date"],
  150. "departure_schengen_area_date": dates["departure_schengen_area_date"],
  151. }
  152. # 测试代码
  153. if __name__ == "__main__":
  154. print("--- 🇨🇳 中国数据 (借用API年龄性别 + 本地化姓名/手机) ---")
  155. print(generate_random_account_detail("CN"))
  156. print("\n--- 🇬🇧 英国数据 (完全依赖API生成) ---")
  157. print(generate_random_account_detail("GB"))