fake_utils.py 11 KB

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