>>> from env_helper import info; info()
页面更新时间: 2024-01-17 15:33:34
运行环境:
Linux发行版本: Debian GNU/Linux 12 (bookworm)
操作系统内核: Linux-6.1.0-17-amd64-x86_64-with-glibc2.36
Python版本: 3.11.2
4.5. 项目:取得当前的天气数据¶
检查天气似乎相当简单:打开 Web
浏览器,点击地址栏, 输入天气网站的
URL
(或搜索一个,然后点击链接), 等待页面加载,跳过所有的广告等。
其实,如果有一个程序,下载今后几天的天气预报,并以
纯文本打印出来,就可以跳过很多无聊的步骤。该程序利用 第11章介绍的
requests
模块,从网站下载数据。 总的来说,该程序将执行以下操作:
从命令行读取请求的位置。
从
OpenWeatherMap.org
下载JSON
天气数据。将
JSON
数据字符串转换成Python
的数据结构。打印今天和未来两天的天气。
因此,代码需要完成以下任务:
连接
sys.argv
中的字符串,得到位置。调用
requests.get()
,下载天气数据。调用
json.loads()
,将JSON
数据转换 为Python
数据结构。打印天气预报。
针对这个项目,打开一个新的文件编辑器窗口, 并保存为 quickWeather.py
。
4.5.1. 第1步:从命令行参数获取位置¶
该程序的输入来自命令行。让 quickWeather.py
看起来像这样:
#! python3
# quickWeather.py - Prints the weather for a location from the command line.
import json, requests, sys
# Compute location from command line arguments.
if len(sys.argv) < 2:
print('Usage: quickWeather.py location')
sys.exit()
location="长春"
# T0D0: Download the JSON data from OpenWeatherMap.org's API.
# T0D0: Load JSON data into a Python variable.
在 Python
中,命令行参数存储在 sys.argv
列表里。 #!
行和
import
语句之后,程序会检查是否有多个 命令行参数(回想一下,
sys.argv
中至少有一个元素 sys.argv[0]
,它包含了Python
脚本的文件名)。 如果该列表中只有一个元素,那么用户没有在命
令行中提供位置,程序向用户提供“Usage(用法)”信息, 然后结束。
命令行参数以空格分隔。命令行参数San Francisco, CA将使
sys.argv
中保存['quickWeather.py','San','Francisco', 'CA']
。
因此,调用 join()方法,将 sys.argv
中除第一个字符串以
外的字符串连接起来。将连接的字符串存储在变量location
中。
4.5.2. 第2步:下载 JSON
数据¶
OpenWeatherMap.org提供了 JSON
格式的实时天气信息。
你的程序只需要下载页面
http://api.openweathermap.org/data/2.5/forecast/daily?q=&cnt=3,
其中是想知道天气的城市。 将以下代码添加到 quickWeather.py
中。
>>> #! python3
>>> # quickWeather.py - Prints the weather for a location from the command line.
>>>
>>> import json, requests, sys
>>>
>>> # Compute location from command line arguments.
>>> if len(sys.argv) < 2:
>>> print('Usage: quickWeather.py location')
>>> sys.exit()
>>> location="长春"
>>> weatherJsonUrl = "http://wthrcdn.etouch.cn/weather_mini?city=%s" % (location)
>>> response = requests.get(weatherJsonUrl)
>>>
>>> try:
>>> response.raise_for_status()
>>> except:
>>> print("网址请求出错")
>>> response = requests.get(weatherJsonUrl)
>>>
>>> weatherData = json.loads(response.text)
>>>
>>> w = weatherData['data']
>>>
>>> # TODO: Load JSON data into a Python variable.
---------------------------------------------------------------------------
gaierror Traceback (most recent call last)
File /usr/lib/python3/dist-packages/urllib3/connection.py:174, in HTTPConnection._new_conn(self)
173 try:
--> 174 conn = connection.create_connection(
175 (self._dns_host, self.port), self.timeout, **extra_kw
176 )
178 except SocketTimeout:
File /usr/lib/python3/dist-packages/urllib3/util/connection.py:73, in create_connection(address, timeout, source_address, socket_options)
69 return six.raise_from(
70 LocationParseError("'%s', label empty or too long" % host), None
71 )
---> 73 for res in socket.getaddrinfo(host, port, family, socket.SOCK_STREAM):
74 af, socktype, proto, canonname, sa = res
File /usr/lib/python3.11/socket.py:962, in getaddrinfo(host, port, family, type, proto, flags)
961 addrlist = []
--> 962 for res in _socket.getaddrinfo(host, port, family, type, proto, flags):
963 af, socktype, proto, canonname, sa = res
gaierror: [Errno -2] Name or service not known
During handling of the above exception, another exception occurred:
NewConnectionError Traceback (most recent call last)
File /usr/lib/python3/dist-packages/urllib3/connectionpool.py:704, in HTTPConnectionPool.urlopen(self, method, url, body, headers, retries, redirect, assert_same_host, timeout, pool_timeout, release_conn, chunked, body_pos, **response_kw)
703 # Make the request on the httplib connection object.
--> 704 httplib_response = self._make_request(
705 conn,
706 method,
707 url,
708 timeout=timeout_obj,
709 body=body,
710 headers=headers,
711 chunked=chunked,
712 )
714 # If we're going to release the connection in ``finally:``, then
715 # the response doesn't need to know about the connection. Otherwise
716 # it will also try to release it and we'll have a double-release
717 # mess.
File /usr/lib/python3/dist-packages/urllib3/connectionpool.py:399, in HTTPConnectionPool._make_request(self, conn, method, url, timeout, chunked, **httplib_request_kw)
398 else:
--> 399 conn.request(method, url, **httplib_request_kw)
401 # We are swallowing BrokenPipeError (errno.EPIPE) since the server is
402 # legitimately able to close the connection after sending a valid response.
403 # With this behaviour, the received response is still readable.
File /usr/lib/python3/dist-packages/urllib3/connection.py:239, in HTTPConnection.request(self, method, url, body, headers)
238 headers["User-Agent"] = _get_default_user_agent()
--> 239 super(HTTPConnection, self).request(method, url, body=body, headers=headers)
File /usr/lib/python3.11/http/client.py:1282, in HTTPConnection.request(self, method, url, body, headers, encode_chunked)
1281 """Send a complete request to the server."""
-> 1282 self._send_request(method, url, body, headers, encode_chunked)
File /usr/lib/python3.11/http/client.py:1328, in HTTPConnection._send_request(self, method, url, body, headers, encode_chunked)
1327 body = _encode(body, 'body')
-> 1328 self.endheaders(body, encode_chunked=encode_chunked)
File /usr/lib/python3.11/http/client.py:1277, in HTTPConnection.endheaders(self, message_body, encode_chunked)
1276 raise CannotSendHeader()
-> 1277 self._send_output(message_body, encode_chunked=encode_chunked)
File /usr/lib/python3.11/http/client.py:1037, in HTTPConnection._send_output(self, message_body, encode_chunked)
1036 del self._buffer[:]
-> 1037 self.send(msg)
1039 if message_body is not None:
1040
1041 # create a consistent interface to message_body
File /usr/lib/python3.11/http/client.py:975, in HTTPConnection.send(self, data)
974 if self.auto_open:
--> 975 self.connect()
976 else:
File /usr/lib/python3/dist-packages/urllib3/connection.py:205, in HTTPConnection.connect(self)
204 def connect(self):
--> 205 conn = self._new_conn()
206 self._prepare_conn(conn)
File /usr/lib/python3/dist-packages/urllib3/connection.py:186, in HTTPConnection._new_conn(self)
185 except SocketError as e:
--> 186 raise NewConnectionError(
187 self, "Failed to establish a new connection: %s" % e
188 )
190 return conn
NewConnectionError: <urllib3.connection.HTTPConnection object at 0x7f13f6489d50>: Failed to establish a new connection: [Errno -2] Name or service not known
During handling of the above exception, another exception occurred:
MaxRetryError Traceback (most recent call last)
File /usr/lib/python3/dist-packages/requests/adapters.py:489, in HTTPAdapter.send(self, request, stream, timeout, verify, cert, proxies)
488 if not chunked:
--> 489 resp = conn.urlopen(
490 method=request.method,
491 url=url,
492 body=request.body,
493 headers=request.headers,
494 redirect=False,
495 assert_same_host=False,
496 preload_content=False,
497 decode_content=False,
498 retries=self.max_retries,
499 timeout=timeout,
500 )
502 # Send the request.
503 else:
File /usr/lib/python3/dist-packages/urllib3/connectionpool.py:788, in HTTPConnectionPool.urlopen(self, method, url, body, headers, retries, redirect, assert_same_host, timeout, pool_timeout, release_conn, chunked, body_pos, **response_kw)
786 e = ProtocolError("Connection aborted.", e)
--> 788 retries = retries.increment(
789 method, url, error=e, _pool=self, _stacktrace=sys.exc_info()[2]
790 )
791 retries.sleep()
File /usr/lib/python3/dist-packages/urllib3/util/retry.py:592, in Retry.increment(self, method, url, response, error, _pool, _stacktrace)
591 if new_retry.is_exhausted():
--> 592 raise MaxRetryError(_pool, url, error or ResponseError(cause))
594 log.debug("Incremented Retry for (url='%s'): %r", url, new_retry)
MaxRetryError: HTTPConnectionPool(host='wthrcdn.etouch.cn', port=80): Max retries exceeded with url: /weather_mini?city=%E9%95%BF%E6%98%A5 (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x7f13f6489d50>: Failed to establish a new connection: [Errno -2] Name or service not known'))
During handling of the above exception, another exception occurred:
ConnectionError Traceback (most recent call last)
Cell In [3], line 12
10 location="长春"
11 weatherJsonUrl = "http://wthrcdn.etouch.cn/weather_mini?city=%s" % (location)
---> 12 response = requests.get(weatherJsonUrl)
14 try:
15 response.raise_for_status()
File /usr/lib/python3/dist-packages/requests/api.py:73, in get(url, params, **kwargs)
62 def get(url, params=None, **kwargs):
63 r"""Sends a GET request.
64
65 :param url: URL for the new :class:`Request` object.
(...)
70 :rtype: requests.Response
71 """
---> 73 return request("get", url, params=params, **kwargs)
File /usr/lib/python3/dist-packages/requests/api.py:59, in request(method, url, **kwargs)
55 # By using the 'with' statement we are sure the session is closed, thus we
56 # avoid leaving sockets open which can trigger a ResourceWarning in some
57 # cases, and look like a memory leak in others.
58 with sessions.Session() as session:
---> 59 return session.request(method=method, url=url, **kwargs)
File /usr/lib/python3/dist-packages/requests/sessions.py:587, in Session.request(self, method, url, params, data, headers, cookies, files, auth, timeout, allow_redirects, proxies, hooks, stream, verify, cert, json)
582 send_kwargs = {
583 "timeout": timeout,
584 "allow_redirects": allow_redirects,
585 }
586 send_kwargs.update(settings)
--> 587 resp = self.send(prep, **send_kwargs)
589 return resp
File /usr/lib/python3/dist-packages/requests/sessions.py:701, in Session.send(self, request, **kwargs)
698 start = preferred_clock()
700 # Send the request
--> 701 r = adapter.send(request, **kwargs)
703 # Total elapsed time of the request (approximately)
704 elapsed = preferred_clock() - start
File /usr/lib/python3/dist-packages/requests/adapters.py:565, in HTTPAdapter.send(self, request, stream, timeout, verify, cert, proxies)
561 if isinstance(e.reason, _SSLError):
562 # This branch is for urllib3 v1.22 and later.
563 raise SSLError(e, request=request)
--> 565 raise ConnectionError(e, request=request)
567 except ClosedPoolError as e:
568 raise ConnectionError(e, request=request)
ConnectionError: HTTPConnectionPool(host='wthrcdn.etouch.cn', port=80): Max retries exceeded with url: /weather_mini?city=%E9%95%BF%E6%98%A5 (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x7f13f6489d50>: Failed to establish a new connection: [Errno -2] Name or service not known'))
我们从命令行参数中得到了location
。为了生成要访问的网址,
我们利用%s
占位符,将 location
中保存的字符串插入 URL
字符串的那个位置。结果保存在 url
中,并将 url
传入
requests.get()
。requests.get()
调用 返回一个Response
对象,它可以通过调用 raise_for_status()
来检查错误。如果不发生异常,
下载的文本将保存在 response.text
中。
4.5.3. 第3步:加载 JSON
数据并打印天气¶
response.text
成员变量保存了一个JSON
格式数据的
大字符串。要将它转换为Python值,就调用json.loads()
函数。JSON数据会像这样:
{'city':{'coord': {'lat': 37.7771, 'Ion': -122.42}, 'country': ' United States of America', 'id': '5391959',
'name': 'San Francisco','population': 0},
'cnt':3,
'cod':200,
'list':[{'clouds': 0,'deg': 233,'dt': 1402344000,'humidity': 58,'pressure': 1012.23, 'speed1: 1.96,
'temp': {'day': 302.29, 'eve': 296.46, 'max': 302.29, 'min':289.77, 'morn': 294.59, 'night': 289.77},
'weather': [{'description':'sky is clear',
'icon':'01d',
--snip--
可以将weatherData
传入pprint.pprint
,查看这个数据。
你可能要查找http://openweathermap.org/ ,找到关于这些
字段含义的文档。例如,在线文档会告诉你,'day’
后面的
302.29是白天的开尔文温度,而不是摄氏或华氏温度。
你想要的天气描述在'nain’
和'description'
之后。
为了整齐地打印出来,在 quickWeather.py
中添加以下代码。
>>> #! python3
>>> # quickWeather.py - Prints the weather for a location from the command line.
>>>
>>> import json, requests, sys
>>>
>>> # Compute location from command line arguments.
>>> if len(sys.argv) < 2:
>>> print('Usage: quickWeather.py location')
>>> sys.exit()
>>> location="长春"
>>> weatherJsonUrl = "http://wthrcdn.etouch.cn/weather_mini?city=%s" % (location)
>>> response = requests.get(weatherJsonUrl)
>>>
>>> try:
>>>
>>> response.raise_for_status()
>>>
>>> except:
>>>
>>> print("网址请求出错")
>>> response = requests.get(weatherJsonUrl)
>>>
>>> weatherData = json.loads(response.text)
>>>
>>> w = weatherData['data']
>>>
>>> print("地点:%s" % w['city'])
>>>
>>> date_a = []
>>>
>>> highTemp = []
>>>
>>> lowTemp = []
>>>
>>> weather = []
>>>
>>>
>>> for i in range(len(w['forecast'])):
>>> date_a.append(w['forecast'][i]['date'])
>>>
>>> highTemp.append(w['forecast'][i]['high'])
>>>
>>> lowTemp.append(w['forecast'][i]['low'])
>>>
>>> weather.append(w['forecast'][i]['type'])
>>>
>>> print('天气地点 %s:' % (location))
>>> print("日期:" + date_a[i])
>>>
>>> print("\t温度:最" + lowTemp[i] + '℃~最' + highTemp[i] + '℃')
>>>
>>> print("\t天气:" + weather[i])
>>>
>>>
>>> print("\n今日着装:" + w['ganmao'])
>>>
>>> print("当前温度:" + w['wendu'] + "℃")
请注意,代码将 weatherData['list’]
保存在变量 w
中,
这将节省一些打字时间。可以用 w[0]
、w[1]
和 w[2]
来取得今天、明天和后天天气的字典。这些字典都有 'weather'
键,其中包含一个列表值。你感兴趣的是
第一个列表项(一个嵌套的字典,包含几个键),下标是0。
这里,我们打印出保存在'main'
和'description'
键
中的值,用连字符隔开。
如果用命令行参数quickWeather.py San Francisco,CA
运行这个程序,输出看起来是这样的:
Current weather in San Francisco, CA:
Clear - sky is clear
Tomorrow:
Clouds - few clouds
Day after tomorrow:
Clear - sky is clear
4.5.4. 第4步:类似程序的想法¶
访问气象数据可以成为多种类型程序的基础。 你可以创建类似程序,完成以下任务:
收集几个露营地点或远足路线的天气预报, 看看哪一个天气最好。
如果需要将植物移到室内,安排一个程序定期 检查天气并发送霜冻警报(第15章介绍了定时调度, 第16章介绍了如何发送电子邮件)。
从多个站点获得气象数据,同时显示,或计算并 显示多个天气预报的平均值。