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?怎么用脚本自动更新的?