fake_utils.py 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  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-Shanghai": {
  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. "fra1": "FRA1SH",
  32. },
  33. "CN-Hangzhou": {
  34. "nat_code": None, # randomuser 不支持 CN,置为 None
  35. "pool_name": "tls.cn.hgh.fr.sentinel",
  36. "location": "Hangzhou",
  37. "province_residence": "Jiangsu",
  38. "nationality": "China",
  39. "phone_country_code": "86",
  40. # --- 本地化生成规则(针对 API 不支持的国家) ---
  41. "local_first_names": ["Wei", "Fang", "Jian", "Hui", "Lei", "Ting", "Peng", "Xia", "Bin", "Jie", "San", "Ming"],
  42. "local_last_names": ["Wang", "Li", "Zhang", "Liu", "Chen", "Yang", "Huang", "Zhao", "Wu", "Zhou"],
  43. "phone_prefix": ["138", "139", "150", "151", "180", "189"], # 中国手机号前缀
  44. "phone_length": 11,
  45. # -----------------------------------------------
  46. "default_first_name": "San",
  47. "default_last_name": "Zhang",
  48. "default_phone": "13800000000",
  49. "fra1": "FRA1HN",
  50. },
  51. "CN-Beijing": {
  52. "nat_code": None, # randomuser 不支持 CN,置为 None
  53. "pool_name": "tls.cn.bjs.fr.sentinel",
  54. "location": "Beijing",
  55. "province_residence": "Beijing",
  56. "nationality": "China",
  57. "phone_country_code": "86",
  58. # --- 本地化生成规则(针对 API 不支持的国家) ---
  59. "local_first_names": ["Wei", "Fang", "Jian", "Hui", "Lei", "Ting", "Peng", "Xia", "Bin", "Jie", "San", "Ming"],
  60. "local_last_names": ["Wang", "Li", "Zhang", "Liu", "Chen", "Yang", "Huang", "Zhao", "Wu", "Zhou"],
  61. "phone_prefix": ["138", "139", "150", "151", "180", "189"], # 中国手机号前缀
  62. "phone_length": 11,
  63. # -----------------------------------------------
  64. "default_first_name": "San",
  65. "default_last_name": "Zhang",
  66. "default_phone": "13800000000",
  67. "fra1": "FRA1PB",
  68. },
  69. "GB-London": {
  70. "nat_code": "gb", # API 支持 GB,直接依赖 API 生成姓名
  71. "pool_name": "tls.gb.lon.fr.sentinel",
  72. "location": "London",
  73. "province_residence": "London",
  74. "nationality": "United Kingdom",
  75. "phone_country_code": "44",
  76. "default_first_name": "James",
  77. "default_last_name": "Smith",
  78. "default_phone": "7400000000",
  79. "fra1": "FRA1LO",
  80. },
  81. "IE-Dublin": {
  82. "nat_code": "ie", # API 支持 GB,直接依赖 API 生成姓名
  83. "pool_name": "tls.ie.dub.fr.sentinel",
  84. "location": "Dublin",
  85. "province_residence": "Dublin",
  86. "nationality": "Ireland",
  87. "phone_country_code": "353",
  88. "default_first_name": "James",
  89. "default_last_name": "Smith",
  90. "default_phone": "0895224562",
  91. "fra1": "FRA1DB",
  92. }
  93. }
  94. # ==========================================
  95. # 2. 辅助生成函数 (单一职责)
  96. # ==========================================
  97. def _fetch_random_user_data(nat_code: str = None) -> Dict[str, Any]:
  98. """
  99. 请求 randomuser API。
  100. 如果 nat_code 为空,则请求全局随机用户(仅用于借用其随机的出生日期和性别)。
  101. """
  102. url = f"https://randomuser.me/api/?nat={nat_code}" if nat_code else "https://randomuser.me/api/"
  103. try:
  104. resp = requests.get(url, timeout=10)
  105. resp.raise_for_status()
  106. raw = resp.json()
  107. results = raw.get("results")
  108. if results and isinstance(results, list):
  109. return results[0]
  110. except Exception:
  111. pass
  112. return {}
  113. def _generate_localized_name(config: Dict[str, Any], api_user: Dict[str, Any]) -> Tuple[str, str]:
  114. """生成姓名:优先使用本地词库,否则使用 API 返回值"""
  115. if "local_last_names" in config and "local_first_names" in config:
  116. return random.choice(config["local_first_names"]), random.choice(config["local_last_names"])
  117. first = api_user.get("name", {}).get("first") or config["default_first_name"]
  118. last = api_user.get("name", {}).get("last") or config["default_last_name"]
  119. return first, last
  120. def _generate_localized_phone(config: Dict[str, Any], api_user: Dict[str, Any]) -> str:
  121. """生成手机号:优先使用本地规则,否则清洗 API 返回值"""
  122. if "phone_prefix" in config:
  123. prefix = random.choice(config["phone_prefix"])
  124. suffix_len = config.get("phone_length", 11) - len(prefix)
  125. suffix = "".join(str(random.randint(0, 9)) for _ in range(suffix_len))
  126. return f"{prefix}{suffix}"
  127. phone_raw = api_user.get("cell") or api_user.get("phone") or ""
  128. phone = re.sub(r"\D", "", phone_raw)
  129. return phone or config["default_phone"]
  130. def _generate_random_dates() -> Dict[str, str]:
  131. """生成合法的随机日期集合(出行、护照等)"""
  132. today = datetime.today()
  133. base_date = today + timedelta(days=random.randint(20, 90))
  134. # 护照日期(避免闰年 replace 报错,采用天数计算)
  135. start_date = today - timedelta(days=5 * 365)
  136. passport_issue = start_date + timedelta(days=random.randint(0, (today - start_date).days))
  137. passport_expiry = passport_issue + timedelta(days=10 * 365 + 2) - timedelta(days=1)
  138. return {
  139. "departure_origin_date": (base_date - timedelta(days=random.randint(0, 2))).strftime("%Y-%m-%d"),
  140. "arrival_schengen_area_date": base_date.strftime("%Y-%m-%d"),
  141. "departure_schengen_area_date": (base_date + timedelta(days=random.randint(2, 15))).strftime("%Y-%m-%d"),
  142. "passport_issue_date": passport_issue.strftime("%Y-%m-%d"),
  143. "passport_expiry_date": passport_expiry.strftime("%Y-%m-%d"),
  144. }
  145. def _generate_email(first_name: str, last_name: str) -> str:
  146. """基于姓名生成随机邮箱"""
  147. email_prefix = re.sub(r"[^a-z0-9]", "", f"{first_name}{last_name}".lower())
  148. if not email_prefix:
  149. email_prefix = f"user{random.randint(100000, 999999)}"
  150. return f"{email_prefix}{random.randint(1000, 9999)}@text.skin"
  151. # ==========================================
  152. # 3. 主函数
  153. # ==========================================
  154. def generate_random_account_detail(country_code: str = "CN") -> Dict[str, Any]:
  155. """
  156. 基于 randomuser 和指定国家配置生成随机账户信息。
  157. """
  158. config = COUNTRY_CONFIGS.get(country_code)
  159. if not config:
  160. raise ValueError(f"Unsupported country code: {country_code}")
  161. # 1. 抓取 API(即使不支持的国家,也可借用其随机返回的年龄/性别)
  162. api_user = _fetch_random_user_data(config.get("nat_code"))
  163. # 2. 解析基础数据(本地化拦截器)
  164. first_name, last_name = _generate_localized_name(config, api_user)
  165. phone_number = _generate_localized_phone(config, api_user)
  166. gender_raw = str(api_user.get("gender", "")).strip().lower()
  167. gender = "Male" if gender_raw == "male" else "Female"
  168. birthday_raw = api_user.get("dob", {}).get("date", "")
  169. birthday = birthday_raw[:10] if birthday_raw else "1990-01-01"
  170. # 3. 各种衍生计算
  171. email = _generate_email(first_name, last_name)
  172. dates = _generate_random_dates()
  173. app_form_suffix = "".join(str(random.randint(0, 9)) for _ in range(11))
  174. passport_no = "".join(random.choices("ABCDEFGHIJKLMNOPQRSTUVWXYZ", k=2)) + \
  175. "".join(random.choices("0123456789", k=7))
  176. fra1_prefix = config.get('fra1')
  177. # LO 伦敦 DB 都柏林 PB 北京 SH 上海 HN 杭州
  178. # 4. 组装组装并返回
  179. return {
  180. "pool_name": config["pool_name"],
  181. "email": email,
  182. "pwd": DEFAULT_PASSWORD,
  183. "location": config["location"],
  184. "visa_type": VISA_TYPE,
  185. "travel_purpose": TRAVEL_PURPOSE,
  186. "application_form_id": f"{fra1_prefix}{app_form_suffix}",
  187. "last_name": last_name,
  188. "first_name": first_name,
  189. "gender": gender,
  190. "birthday": birthday,
  191. "nationality": config["nationality"],
  192. "province_residence": config["province_residence"],
  193. "passport_type": PASSPORT_TYPE,
  194. "passport_no": passport_no,
  195. "passport_issue_date": dates["passport_issue_date"],
  196. "passport_expiry_date": dates["passport_expiry_date"],
  197. "phone_country_code": config["phone_country_code"],
  198. "phone_number": phone_number,
  199. "departure_origin_date": dates["departure_origin_date"],
  200. "arrival_schengen_area_date": dates["arrival_schengen_area_date"],
  201. "departure_schengen_area_date": dates["departure_schengen_area_date"],
  202. }
  203. # 测试代码
  204. if __name__ == "__main__":
  205. print("--- 🇨🇳 中国数据 (借用API年龄性别 + 本地化姓名/手机) ---")
  206. print(generate_random_account_detail("CN"))
  207. print("\n--- 🇬🇧 英国数据 (完全依赖API生成) ---")
  208. print(generate_random_account_detail("GB"))