IPWeatherObject.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321
  1. # @Author : Rocky
  2. # @File : IPWeatherObject.py
  3. # @Time : 2023/8/16 8:56
  4. import datetime
  5. import json
  6. import geoip2.webservice
  7. import requests
  8. from Model.models import IPAddr, OpenWeatherMapCallCount
  9. from Object.AWS.S3Email import S3Email
  10. from Object.RedisObject import RedisObject
  11. from Service.CommonService import CommonService
  12. from Ansjer.config import LOGGER, CONFIG_INFO
  13. class IPQuery:
  14. """
  15. 阿里云IP地址查询
  16. https://market.aliyun.com/products/57002003/cmapi021970.html?spm=5176.2020520132.recommend-card.dbottom-suggestion.33e17218GYjWDt#sku=yuncode15970000020
  17. """
  18. def __init__(self, ip):
  19. self.appcode = 'd7d63b34b1d54214be446608a57ff0a2'
  20. self.host = 'https://c2ba.api.huachen.cn'
  21. self.path = '/ip'
  22. self.district = '' # 区
  23. self.city = '' # 市
  24. self.region = '' # 省/州
  25. self.country_id = ''
  26. param = 'ip=' + ip
  27. url = self.host + self.path + '?' + param
  28. # 获取ip的区级和城市信息
  29. headers = {'Authorization': 'APPCODE ' + self.appcode}
  30. res = requests.get(url=url, headers=headers)
  31. if res.status_code == 200:
  32. # 反序列化响应数据
  33. res_data = eval(res.content.decode('utf-8'))
  34. if res_data['ret'] == 200:
  35. district = res_data['data']['district']
  36. city = res_data['data']['city']
  37. region = res_data['data']['region']
  38. country_id = res_data['data']['country_id']
  39. # 国内城市数据不为空字符,拼上'市'字
  40. if country_id == 'CN' and city != '':
  41. city += '市'
  42. # ip地址信息存表或更新
  43. ip_addr_qs = IPAddr.objects.filter(ip=ip, is_geoip2=False)
  44. if ip_addr_qs.exists():
  45. ip_addr_qs.update(district=district, city=city, region=region, country_code=country_id)
  46. else:
  47. IPAddr.objects.create(ip=ip, district=district, city=city, region=region, country_code=country_id)
  48. self.city = city # 市
  49. self.region = region # 省/州
  50. if district != '':
  51. self.district = district
  52. elif city != '':
  53. self.district = city
  54. self.country_id = country_id
  55. class WeatherInfo:
  56. """
  57. 阿里云墨迹天气服务
  58. https://market.aliyun.com/products/57096001/cmapi013828.html?spm=5176.2020520132.101.19.2b8f7218NuiGPd#sku=yuncode782800000
  59. """
  60. def __init__(self, city_id):
  61. self.appcode = 'd7d63b34b1d54214be446608a57ff0a2'
  62. self.headers = {'Authorization': 'APPCODE ' + self.appcode,
  63. 'Content-Type': 'application/x-www-form-urlencoded'}
  64. self.city_id = city_id
  65. def get_city_weather(self):
  66. url = "https://aliv18.data.moji.com/whapi/json/alicityweather/condition" # 获取实时天气
  67. data = {'cityId': self.city_id}
  68. response = requests.post(url, headers=self.headers, data=data, verify=False)
  69. if response.status_code == 200:
  70. result = response.json()
  71. if result['code'] == 0:
  72. # 返回温湿度
  73. return result['data']['condition']['temp'], result['data']['condition']['humidity']
  74. return None, None
  75. def get_city_24_weather(self):
  76. url = 'https://aliv18.data.moji.com/whapi/json/alicityweather/forecast24hours' # 获取城市24小时天气
  77. data = {'cityId': self.city_id}
  78. response = requests.post(url, headers=self.headers, data=data, verify=False)
  79. if response.status_code == 200:
  80. result = response.json()
  81. if result['code'] == 0:
  82. # 返回天气列表
  83. return result['data']['hourly']
  84. return None
  85. class GeoIP2:
  86. """
  87. MaxMind GeoIP2查询国外ip
  88. 同时保存ip信息
  89. https://www.maxmind.com/
  90. """
  91. def __init__(self, ip):
  92. self.account_id = 938644
  93. self.license_key = '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'
  94. self.license_key_sandbox = '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'
  95. try:
  96. with geoip2.webservice.Client(self.account_id, self.license_key) as client:
  97. # You can also use `client.city` or `client.insights`
  98. # `client.insights` is not available to GeoLite2 users
  99. response = client.city(ip)
  100. # 经纬度精确到小数点两位
  101. lat = round(response.location.latitude, 2)
  102. lon = round(response.location.longitude, 2)
  103. # 获取中文或英文城市名,省/州
  104. city = ''
  105. city_names = response.city.names
  106. city_cn = city_names.get('zh-CN')
  107. if city_cn:
  108. city = city_cn
  109. elif city_names.get('en'):
  110. city = city_names['en']
  111. region = ''
  112. subdivisions_names = response.subdivisions[0].names
  113. region_cn = subdivisions_names.get('zh-CN')
  114. if region_cn:
  115. region = region_cn
  116. elif subdivisions_names.get('en'):
  117. region = subdivisions_names['en']
  118. country_code = response.country.iso_code
  119. # 保存ip信息
  120. ip_addr_data = {
  121. 'ip': ip,
  122. 'lat': lat,
  123. 'lon': lon,
  124. 'city': city,
  125. 'region': region,
  126. 'country_code': country_code,
  127. 'is_geoip2': True
  128. }
  129. IPAddr.objects.create(**ip_addr_data)
  130. except Exception as e:
  131. LOGGER.info('GeoIP2解析ip异常:error_line:{}, error_msg:{}'.format(e.__traceback__.tb_lineno, repr(e)))
  132. class OpenWeatherMap:
  133. """
  134. OpenWeatherMap查询国外天气服务
  135. https://openweathermap.org/
  136. """
  137. def __init__(self, lat, lon):
  138. self.appid = '7a6cd7dfeb034ededa451ed575788857'
  139. self.lat = lat # 纬度
  140. self.lon = lon # 经度
  141. def get_weather(self):
  142. """
  143. 从缓存查询天气数据
  144. 或者查询当前天气,并缓存数据
  145. @return: temp, humidity
  146. """
  147. # 查询缓存数据
  148. today = datetime.datetime.today()
  149. now_time = datetime.datetime(today.year, today.month, today.day, today.hour)
  150. str_time = now_time.strftime('%Y-%m-%d %H:%M:%S')
  151. time_stamp = CommonService.str_to_timestamp(str_time)
  152. key = 'weather:lat:{}_lon:{}_time_stamp:{}'.format(self.lat, self.lon, time_stamp)
  153. redis_obj = RedisObject()
  154. weather = redis_obj.get_data(key)
  155. if weather:
  156. temp, humidity = weather.split('/')
  157. else:
  158. temp, humidity = self.get_current_weather(str_time[:7])
  159. if temp is not None and humidity is not None:
  160. key = 'weather:lat:{}_lon:{}_time_stamp:{}'.format(self.lat, self.lon, time_stamp)
  161. redis_obj.set_ex_data(key, '{}/{}'.format(temp, humidity), 3600)
  162. return temp, humidity
  163. def get_current_weather(self, month):
  164. """
  165. 根据经纬度获取当前天气
  166. @param month: 年月份
  167. @return: temp, humidity
  168. """
  169. url = 'https://api.openweathermap.org/data/2.5/weather'
  170. params = {
  171. 'lat': self.lat,
  172. 'lon': self.lon,
  173. 'appid': self.appid,
  174. 'units': 'metric' # 公制单位,温度单位:摄氏度
  175. }
  176. res = requests.get(url=url, params=params, timeout=10)
  177. # 记录调用次数
  178. open_weather_map_call_count_qs = OpenWeatherMapCallCount.objects.filter(month=month).values('count')
  179. if not open_weather_map_call_count_qs.exists():
  180. OpenWeatherMapCallCount.objects.create(month=month)
  181. else:
  182. count = open_weather_map_call_count_qs[0]['count'] + 1
  183. open_weather_map_call_count_qs.update(count=count)
  184. # 调用次数超过750,000,邮件提醒
  185. warning_count = 750000
  186. if count > warning_count:
  187. redis_obj = RedisObject()
  188. key = 'open_weather_map_call_count_warning_time_limit'
  189. time_limit = redis_obj.get_data(key)
  190. if not time_limit:
  191. # 限制一天提醒一次
  192. redis_obj.set_data(key, 1, 60 * 60 * 24)
  193. subject = '邮件提醒'
  194. data = '{}服open weather调用次数大于{}'.format(CONFIG_INFO, warning_count)
  195. S3Email().send_email(subject, data, 'servers@ansjer.com')
  196. if res.status_code != 200:
  197. return None, None
  198. res_data = eval(res.text)
  199. if res_data['cod'] != 200:
  200. return None, None
  201. temp = str(int(res_data['main']['temp']))
  202. humidity = str(int(res_data['main']['humidity']))
  203. return temp, humidity
  204. class Findip:
  205. """
  206. findip 查询国外ip
  207. 同时保存ip信息
  208. https://www.findip.net/Main/Index
  209. """
  210. def __init__(self, ip):
  211. self.token = 'bb51156441ec401caec0476864ec2b93'
  212. self.ip = ip
  213. self.lat = 0.0
  214. self.lon = 0.0
  215. self.city = ''
  216. self.region = ''
  217. self.country_code = ''
  218. try:
  219. LOGGER.info('FindIP解析ip:{}'.format(self.ip))
  220. # 调用findip API获取IP信息
  221. self._get_ip_info()
  222. # 保存ip信息
  223. ip_addr_data = {
  224. 'ip': self.ip,
  225. 'lat': self.lat,
  226. 'lon': self.lon,
  227. 'city': self.city,
  228. 'region': self.region,
  229. 'country_code': self.country_code,
  230. 'is_geoip2': True # 是否为GeoIP2解析
  231. }
  232. LOGGER.info('FindIP解析ip成功:{}'.format(ip_addr_data))
  233. IPAddr.objects.create(**ip_addr_data)
  234. except Exception as e:
  235. LOGGER.error('FindIP解析ip异常:error_line:{}, error_msg:{}'.format(
  236. e.__traceback__.tb_lineno, repr(e)
  237. ))
  238. def _get_ip_info(self):
  239. """调用findip API并解析返回结果"""
  240. try:
  241. # 构建API请求URL
  242. url = f"https://api.findip.net/{self.ip}/?token={self.token}"
  243. response = requests.get(url, timeout=10)
  244. # 检查请求是否成功
  245. if response.status_code == 200:
  246. data = response.json()
  247. self._parse_response(data)
  248. else:
  249. raise Exception(f"API请求失败,状态码: {response.status_code}")
  250. except requests.RequestException as re:
  251. raise Exception(f"请求异常: {str(re)}")
  252. except json.JSONDecodeError as jde:
  253. raise Exception(f"JSON解析异常: {str(jde)}")
  254. def _parse_response(self, data):
  255. """解析API返回的JSON数据并提取所需字段"""
  256. # 提取经纬度并保留两位小数
  257. if 'location' in data and data['location']:
  258. self.lat = round(data['location'].get('latitude', 0.0), 2)
  259. self.lon = round(data['location'].get('longitude', 0.0), 2)
  260. # 提取国家代码
  261. if 'country' in data and data['country'] and 'iso_code' in data['country']:
  262. self.country_code = data['country']['iso_code']
  263. # 提取城市名称(优先中文,其次英文)
  264. if 'city' in data and data['city'] and 'names' in data['city']:
  265. names = data['city']['names']
  266. self.city = names.get('zh-CN', names.get('en', ''))
  267. # 提取地区名称(优先中文,其次英文)
  268. if 'subdivisions' in data and data['subdivisions']:
  269. # 取第一个细分区域(通常是省/州级别)
  270. first_subdivision = data['subdivisions'][0]
  271. if 'names' in first_subdivision:
  272. names = first_subdivision['names']
  273. self.region = names.get('zh-CN', names.get('en', ''))