試験運用中なLinux備忘録・旧記事

はてなダイアリーで公開していた2007年5月-2015年3月の記事を保存しています。

Pythonのurllibでダウンロードしたファイルのタイムスタンプをサーバ上の最終更新日時に合わせる

wgetというCLIのダウンロードツールは、HTTPサーバの返す「Last-Modified:」ヘッダをもとに、ダウンロードしたファイルの最終更新日時を設定する。
しかし、大部分のダウンロードツールやWebブラウザなどでは、ダウンロードしたファイルのタイムスタンプは、ダウンロードした時点のものとなり、「urllibを使用してファイルをローカルに保存する」で書いたコードも同様となる。
Pythonのurllibを使用して、wgetのように「Last-Modified:」ヘッダによるタイムスタンプ設定を行うためには、

  1. HTTPヘッダから「Last-Modified:」ヘッダを探して解析
  2. 得られたタイムスタンプ情報をローカル時間としてファイルに適用

という操作が必要となり、「urllibを使用してファイルをローカルに保存する」のコードをもとにしてこれらの変更を行ったコードは下のようになる。

#! /usr/bin/python
# -*- encoding: utf-8 -*-

import urllib
import time
import sys
import os

class MyURLopener(urllib.FancyURLopener):
  version = "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0)"  # User-Agent:
  def open_unknown(self, given_url, data):
    print u"URLではないため、処理できません: %s".encode("utf-8") % given_url
    sys.exit(1)

url = "http://www.python.jp/pub/doc_jp/html-2.4.tar.bz2"
#url = "hoge://www.python.jp/pub/doc_jp/html-2.4.tar.bz2"
localfile = "/tmp/%s" % os.path.basename(url)

opener = MyURLopener({})

try:
  (filename, headers) = opener.retrieve(url, localfile)
except IOError:
  print u"取得に失敗しました".encode("utf-8")
  sys.exit(1)
opener.cleanup()  # キャッシュの削除

## 最終更新日時をローカル上のファイルのタイムスタンプとしてセットする
for l in str(headers).splitlines():
  if "Last-Modified: " in l:
    try:
      ## Last-Modified: の値を解析してローカルのタイムスタンプを得る
      timestamp = time.mktime(time.strptime(l, "Last-Modified: %a, %d %b %Y %H:%M:%S GMT")) - time.timezone
    except ValueError:
      print u"エラー: URL \"%s\" の最終更新日時の解析に失敗しました".encode("utf-8") % url
      sys.exit(1)
    try:
      os.utime(localfile, (timestamp, timestamp))  # 場所, (アクセス, 更新)
    except OSError:
      print u"エラー: ファイル \"%s\" のタイムスタンプ操作に失敗しました".encode("utf-8") % localfile
      sys.exit(1)

ダウンロードされるファイルは前回と同じくPythonのドキュメントの日本語訳(バージョン2.4のHTML版)で、/tmp/以下に保存される。
設定されるタイムスタンプ(最終アクセス日時と最終更新日時)は「2006/06/27 12:53」となり、wgetでダウンロードした場合と同じになる。
実際のコードにおける作業としては

  1. HTTPヘッダを文字列型にして行単位で処理し、「Last-Modified:」の部分をtime.strptime()関数で解析(記号の意味は下のリファレンスのURLを参照)
  2. ローカル時間のタイムスタンプを得る(time.mktime()関数に上の結果を渡したものからtime.timezoneを引く)
  3. os.utime()関数*1でタイムスタンプを変更

となる。

関連記事:

参考URL:

*1:1番目の引数がファイルの場所、2番目の引数が(最終アクセス, 最終更新)のタプル