正在加载...

AppEngine之Cron Jobs应用实例

作者:混沌 十一月 28th, 2009

  副标题: 将博客迁移到AppEngine的图片迁移处理问题

  花了2天的时间将博客迁移到Google AppEngine平台,于今天正式迁移完成.留下此文备查之.

博客程序采用了Micolog这个开源程序.关于此程序的安装配置网上有不少的资料,这里就不作说明了.

但是图片的迁移问题很棘手,网上没有现成可用的方案.

于是考虑自行写代码处理之. 考虑到 AppEngine平台的一个限制: "应用程序必须在 30 秒钟内发出响应". 这说明一次性批量导入数据的方案肯定不可行, 必须要分批导入.  而又希望该程序能够自动进行导入,于是我想到到了:AppEngine的Cron Jobs功能应用能够解决这个问题.

 基本思路:

      前提1) 修改model.py: 为"class Entry(BaseModel)", 添加一个字段:imageloaded=db.BooleanProperty(default=False) 表示该片文章中的图片是否已经导入完成.

      前提2) 已经将所有博客数据已经导入到micolog.  而图片链接全部以外链的方式存在.

      通过Cron Jobs定时(比如每1分钟)驱动一次导入程序(/cron/imageloader),该导入程序每次执行只导入少量图片(比如4张图片). 

      而该导入程序的流程: 
              1)获取Entry中满足imageloaded=False的条记录
              2)获取内容文本中所有目标图片的链接(通过正则表达式:  "(http://([^"~]+?\.(jpg|png|gif)))"  来抓取 )
              3)循环下载图片
              4)保存图片到Media表中,并且获取新的图片链接
              5)将内容文本中的原图片链接置换成新的图片链接
              6)设置imageloaded=True 并且保存新的内容文本到数据库. 本次导入结束
       如果本次下载图片有失败的情况,或者本次下载图片数量大于指定量(比如4个图片),依然设置imageloaded=False并保存退出.

此方案的优点:  1)自动.  一旦配置成功, 不要手工干预.   2)比较独立, 如果采用非micolog的GAE程序,经过少量的修改,也可适用.  3)该程序不但在迁移阶段起作用,而且在 新发布或修改文章时,也会自动将外链图片保存到micolog本地中.  4)和目标博客程序耦合度很低,不影响原程序的稳定性

下面附上相关的配置和代码(配合micolog使用,其它GAE程序需要少量修改):

app.yaml   ------ 修改, 新加配置

- url: /cron/imageloader(/.*)?
  script: imageloader.py
  login: admin

cron.yaml  ---- 新增 放于app.yaml 同级目录

cron:
- description: image loader
  url: /cron/imageloader
  schedule: every 1 minutes
  timezone: Asia/Shanghai

imageloader.py  --- 新增 导入程序

# -*- coding: utf-8 -*-
from google.appengine.ext import webapp
from google.appengine.ext.webapp.util import run_wsgi_app
from google.appengine.ext import db
from google.appengine.api import memcache
import re,logging
import cgi
import urllib

from  model import Media
from  model import Entry

ImagePattern=re.compile(r'"(http://(([^"~]+?\.(jpg|png|gif))|(www\.forkosh\.dreamhost\.com/mimetex\.cgi\?[^"]+)))"')
def downloadimage(url):    
    bits=None
    mtype=None
    try: 
        urlopen=urllib.URLopener()  
        fp = urlopen.open(url)  
        bits = fp.read()
        mtype=fp.headers['Content-Type']   
        fp.close()    
    except :   
        logging.info('Download Error! :'+ url)
        return None
    logging.info('Download OK! :'+ url)
    return (mtype,bits)   
def imageloadertask(postid):
    test = Entry.gql('LIMIT 1').get()
    if test.imageloaded == None :
        all=Entry.all()
        for t in all :
            t.imageloaded = False
            t.put()
        return '更新数据库,补充新添加的字段'
    if postid == None or postid == '' :
      entry = Entry.gql("WHERE imageloaded = False").get()
    else:
      entry = Entry.get_by_id(int(postid))
    if entry == None :
      return '没有要处理的文章'
    title=entry.title   
    content =  entry.content
    entry.imageloaded=True
    l=ImagePattern.findall(content)
    count = 0  #计数器. 为了防止处理超时,限定每次最多下载4个图片
    for e in l :
        count += 1
        if count > 4 :
            entry.imageloaded=False
            logging.info('Donwload Image Count > 4, Exit!')
            break           
        #获取必要参数
        url=e[0]
        name=e[1]
        #下载图片
        rs=downloadimage(url)
        if rs :                      
            #保存图片
            bits=db.Blob(str(rs[1]))
            media=Media(name=name,mtype=rs[0],bits=bits)
            media.put()             
            #置换图片链接
            change='"/media/'+str(media.key())+'"'
            logging.info('Save Image To : '+ change)
            #tmpPattern=re.compile('"'+url.replace('?','\?')+'"')
            #content=tmpPattern.sub(change,content)
            content = content.replace('"'+url+'"',change)
        else:
            entry.imageloaded=False
    entry.content = content
    entry.put()
    if len(l)>0 :
        memcache.delete('/'+entry.link)
    logging.info('Imageloader OK')
    return title       

class imageloader(webapp.RequestHandler):
  def get(self):
    title=imageloadertask(self.request.get("postid"))    
    self.response.out.write('<html><head>')
    self.response.out.write('<meta http-equiv="Content-Type" content="text/html;charset=utf-8;" />')
    self.response.out.write('</head><body>')
    self.response.out.write(cgi.escape(title + ' OK!'))
    self.response.out.write('</body></html>')

application = webapp.WSGIApplication([('/cron/imageloader', imageloader)],
                                     debug=True)

def main():
  run_wsgi_app(application)

if __name__ == "__main__":
  main()

第一少奶孙宁温

相关阅读:

“AppEngine之Cron Jobs应用实例” 共有4条留言

我要留言

麻烦,计算一下:3+9