Categories

天气预报签名档

weather也不知道有多久了,从接近开学一直到现在,杭州天天都在下雨,估计至少有二十天了吧。实在是一件让人非常郁闷的事。之前看到寒仔在 cc98 上的签名档图片是根据时间动态生成的,觉得很好玩,于是也决定弄一个动态的签名档玩玩,正好就做天气预报好了。

首先要选定一个天气来源。之前听说 iGoogle 里面的天气预报小工具预报得非常不准,除了“Current”里的天气情况和目前室外的情况差不多之外,预报的结果似乎完全不可信。 ^_^bb 其实我也完全没有看天气预报的习惯,反正我天天都带着伞的。那么我也干脆弄一个“即时”天气好了,显示当前天气,可以“足不出户,知窗外天气”——关键是要做得漂亮些。但是我又懒得去找那些天气相关的图片素材,最后随便挑选了几个天气来源,就直接选了 Yahoo Weather

Yahoo Weather 提供了 RSS feed 形式的详细天气预报,不过由于我想要他的 HTML 页面上那个漂亮的图标,就直接去解析他的 HTML 页面了。和 Google 那种一看就是机器生成,说不定还是故意经过混淆、压缩过的乱糟糟的 HTML 页面不同,Yahoo 的页面结构清晰得多,用正则表达式很轻松地就把温度、天气图标和背景图片给提取出来了。

接下来是要把这几个东西拼成一个图片,我用 Python Imaging Library (PIL) 来做这件事情。首先创建一个空的图片,然后把背景粘贴进去。Yahoo Weather 在白天和晚上的时候使用不同的背景,并在 HTML 中有标识,所以当能在 HTML 页面中找到相应的标识 pat_night 的时候就是用晚上的背景:

img = Image.new('RGBA', (240, 140))
if pat_night.search(page):
    img.paste(background_nt, (40, 46), background)
else:
    img.paste(background, (40, 46), background)

PIL 提供的图片粘贴函数直接粘贴的话,会把整个区域都替换掉,即时你粘贴的是一个带 Alpha 通道的透明 PNG 图片也是如此,不过可以在第三个参数那里再传递一下这个图片本身,这是只有那些不透明的像素才会被粘贴(并覆盖)过去。

用类似的办法把天气图标粘贴过去之后,就剩下把温度信息写上去了。在 PIL 中要在一个图片上进行绘制,需要创建一个 ImageDraw 对象:

draw = ImageDraw.Draw(img)
draw.text((180, 53), '%-2sC'%degree, font=font)
draw.text((203, 53), 'o', font=font_small)

不知道是字体的原因还是编码处理的原因,我在字符串里直接写度数的符号画出来是乱码,所以我只好用小号字体画了个小写的 ‘o’ 。PIL 中可以很方便地使用 TrueType 字体,就像这样:

font = ImageFont.truetype('font.ttf', 24)

画完之后,ImageDraw 对象就不再需要了,最终图片保存到文件中即可:

del draw
img.save('weather.png', 'PNG')

接下来只要通过 Web Server 让这个图片可以通过网络被访问到,然后再让签名档图片指向对应的 URL 就可以了。当然,这个脚本要定期地更新天气情况并重新生成图片。完整代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#!/usr/bin/python
 
import urllib2
import re
import time
from StringIO import StringIO
 
from PIL import Image, ImageDraw, ImageFont
 
degree = None
icon = None
icon_url_prev = None
background = Image.open('bg.png')
background_nt = Image.open('bg_night.png')
font = ImageFont.truetype('font.ttf', 24)
font_small = ImageFont.truetype('font.ttf', 10)
pat_degree = re.compile(r'<h3>(\d+)&(?:#176|deg);</h3>')
pat_icon = re.compile('<div class="forecast-icon" style="background:url\\(\'' + 
        '([^\']+)\'\\);')
pat_night = re.compile(r'<div id="yw-forecast" class="night">')
 
while True:
    page = urllib2.urlopen('http://weather.yahoo.com/forecast/CHXX0044_c.html').read()
    #page = urllib2.urlopen('http://weather.yahoo.com/forecast/USNY0996.html').read()
 
    degree = pat_degree.search(page).group(1)
    icon_url = pat_icon.search(page).group(1)
    if icon_url_prev != icon_url:
        icon = Image.open(StringIO(urllib2.urlopen(icon_url).read()))
 
    img = Image.new('RGBA', (240, 140))
    if pat_night.search(page):
        img.paste(background_nt, (40, 46), background)
    else:
        img.paste(background, (40, 46), background)
    img.paste(icon, (5, 5), icon)
    draw = ImageDraw.Draw(img)
    draw.text((180, 53), '%-2sC'%degree, font=font)
    draw.text((203, 53), 'o', font=font_small)
    del draw
 
    img.save('weather.png', 'PNG')
    icon_url_prev = icon_url
 
    print '%s deg. [%s]' % (degree, time.ctime(time.time()))
    time.sleep(10*60)

BTW: 本文开头那个图片只是一个普通的效果图,并不是实时更新的。 😉

15 comments to 天气预报签名档

  • 圆圆

    第17行那个 ° 是什么意思呢?

  • 圆圆

    @圆圆
    这个留言系统不好啊……555
    明明是写的 & # 176,怎么变成小圆圆了呢……

  • @圆圆
    汗…… ^_^bb
    因为 &#176; 确实就是 ° 就是“度数”的符号。这种写法写的东西叫做 HTML Entities ,一些常用的就是有名字的,比如 &lt; 是 < ,就是 less than ,其他的是用字符编码对应的数字来表示的。

  • 圆圆

    @pluskid
    原来如此~ 诶话说 w3schools 的页面风格好像换了呢:O

  • °°

    要是这样会怎么样呢哈哈?

  • °°

    哈哈变成小圆圆啦!Hooray!

  • ºº

    那个太小,像猪鼻子……

  • 圆圆

    这个也像,算了,还是换回圆圆吧。谢谢 kid,晚安啦啦啦

  • °

    哇,果然啊

  • °

    我运行后显示,怎么回事?

    Traceback (most recent call last):
      File "D:\Desktop\temp\temp.py", line 23, in 
        page = urllib2.urlopen('http://weather.yahoo.com/forecast/CHXX0044_c.html').read()
      File "C:\Python26\lib\urllib2.py", line 124, in urlopen
        return _opener.open(url, data, timeout)
      File "C:\Python26\lib\urllib2.py", line 383, in open
        response = self._open(req, data)
      File "C:\Python26\lib\urllib2.py", line 401, in _open
        '_open', req)
      File "C:\Python26\lib\urllib2.py", line 361, in _call_chain
        result = func(*args)
      File "C:\Python26\lib\urllib2.py", line 1130, in http_open
        return self.do_open(httplib.HTTPConnection, req)
      File "C:\Python26\lib\urllib2.py", line 1105, in do_open
        raise URLError(err)
    URLError: 
  • @&#176
    恩?看起来像是网络错误,你那边能打开 Yahoo 天气那个页面吗?

  • shwg

    unicode文字需要明示指定,38L在字符串前加上u就好了。如下:
    draw.text((180, 53), u’%-2s℃’%degree, font=font)

  • yw

    原来是zju的师兄,感觉师兄你super 厉害啊!有时间自己也在98上弄一个

  • yw

    想问一下是放到什么Web Server?怎么用脚本自动更新的?