ひきメモ

2008-02-10

[]BeautifulSoupを使ってGoogleの表示順位を調べる

BeautifulSoupHTML解析用のライブラリです。htmllib.HTMLParserやHTMLPaprser.HTMLParserと違い、正しくないHTMLも扱えるようです。これを使ってGoogleの表示順位を調べるスクリプトを書いてみました。

#!python
# vim:fileencoding=utf-8

import re
import sys
import time
import urllib2
import urlparse
from BeautifulSoup import BeautifulSoup

g_url = "http://www.google.co.jp/search?hl=ja&num=100&q="
next_text = u"次へ"
interval = 3
client_encoding = "cp932"
server_encoding = "utf-8"

try:
    keyword, url = sys.argv[1:]
except ValueError:
    sys.exit()
print "keyword:", keyword
print "url    :", url

opener = urllib2.build_opener()
opener.addheaders = [('User-agent', 'Mozilla/5.0')]

keyword = keyword.decode(client_encoding).encode(server_encoding)
search_url = g_url + urllib2.quote(keyword)
rank = 0
page = 1
while search_url:
    print "\rpage : %d" % page,
    sys.stdout.flush()
    html = opener.open(search_url).read()
    soup = BeautifulSoup(html) # HTMLを解析
                               # Unicode文字列に変換される
    
    # キャッシュや関連ページへのリンクを除外したいので
    # class属性が"l"のAタグだけを取得
    for a in soup.findAll("a", {"class":"l"}):
        rank += 1
        href = a["href"] # href属性でURLを取得
        # 指定されたURLが見つかれば、結果を表示して終了
        if href.startswith(url):
            # Aタグで囲まれた文字列を取得
            title = "".join([c.string for c in a.contents])
            print "\nrank :", rank
            print "href :", href
            print "title:", title
            search_url = ""
            break

    # 指定したURLが見つからなければ、次の検索結果を調べる
    else:
        # 次の検索結果へのリンクを取得
        next = soup.find(lambda tag: tag.name=="a" and
                tag.b and tag.b.string==next_text)
        if next:
            search_url = urlparse.urljoin(g_url, next["href"])
            page += 1
            time.sleep(interval)
        else: # 次へのリンクが見つからなければ終了
            print u"圏外です"
            search_url = ""
$ python test.py "python インストール" http://www.hlj.com/~tanoue/            
keyword: python インストール
url    : http://www.hlj.com/~tanoue/
page : 2
rank : 158
href : http://www.hlj.com/~tanoue/Python/Mac/mpy00.html
title: Mac de Python

2008-02-06

[]htmllib.HTMLParserでリンクを抽出

#!python
# vim:fileencoding=utf-8

from htmllib import HTMLParser
from formatter import NullFormatter
import urllib2
from urlparse import urlparse

class ExtractTextLinkParser(HTMLParser):
    
    def __init__(self):
        HTMLParser.__init__(self, NullFormatter())
        self.links = []

    def anchor_bgn(self, href, name, type): # <a>が見つかった場合の処理
        HTMLParser.anchor_bgn(self, href, name, type)
        self.save_bgn() # テキストデータの保存を開始

    def anchor_end(self):      # </a>が見つかった場合の処理
        url = self.anchor
        text = self.save_end() # 保存されたテキストデータを取得
        if url and text:
            self.links.append((url, text))
        self.anchor = None

def get_links(url):
    response = urllib2.urlopen(url)
    parser = ExtractTextLinkParser() 
    parser.feed(response.read())    
    parser.close()
    return parser.links

links = get_links("http://b.hatena.ne.jp/hotentry")
links = [l for l in links if urlparse(l[0])[0]]
for url, title in links[5:15]:
    print "[%s:title=%s]" % (url, title.decode("utf-8", "replace"))
日本の携帯を高くしている真犯人は
テキストエディタでWebサイト構築をガンバル人へ(1/3) − @IT
ウェブ制作・プログラマー・デザイナーのためのチートシート集 | コリス
404 Blog Not Found:38歳までに知ることになる、22歳の自分に教えてあげたいたった1つのこと
Gmailアカウント間でのメール移転方法・複数Gmailアカウントの処理に困っている人に朗報! | Google Mania - グーグルの便利な使い方

HTMLParser.HTMLParserの場合は複数のタグを処理する場合、ifで分岐させる必要がありますが、htmllib.HTMLParserの場合はタグごとにメソッドが用意されています。また、単にURLだけを取得したいならサブクラスを作らなくても可能です。

>>> r = urllib2.urlopen("http://b.hatena.ne.jp/hotentry")
>>> p = HTMLParser(NullFormatter())
>>> p.feed(r.read())
>>> p.close()
>>> links = p.anchorlist # URLのリストを取得
>>> print "\n".join([l for l in links if urlparse(l)[0]][5:10])
http://www.phs-mobile.com/black/black33.html
http://www.atmarkit.co.jp/fwcr/rensai/freeauthoring06/freeauthoring06_1.html
http://coliss.com/articles/build-websites/operation/work/796.html
http://blog.livedoor.jp/dankogai/archives/50997519.html
http://google-mania.net/archives/891

[]HTMLParser.HTMLParserでリンクを抽出

#!python
# vim:fileencoding=utf-8

from HTMLParser import HTMLParser
import urllib2
from urlparse import urlparse

class ExtractTextLinkParser(HTMLParser):
    
    def __init__(self):
        HTMLParser.__init__(self)
        self.links = []
        self.url = ""
        self.text = ""

    def handle_starttag(self, tag, attrs): # 開始タグを見つけた場合の処理
        if tag == "a":          # タグ、属性名は全て小文字
            attrs = dict(attrs) # ((属性名, 値), ...) => {属性名:値, ...}
            if "href" in attrs:
                self.url = attrs["href"]

    def handle_endtag(self, tag): # 終了タグを見つけた場合の処理
        if tag == "a":
            if self.text:         
                self.links.append((self.url, self.text))
            self.url = self.text = ""
    
    def handle_data(self, data): # 開始・終了タグに囲まれた中身の処理
        if self.url:             
            self.text += data    

def get_links(url):
    response = urllib2.urlopen(url)
    parser = ExtractTextLinkParser() 
    parser.feed(response.read())    
    parser.close()
    links = parser.links
    return [l for l in links 
            if l[0].find("://") != -1 and not l[0].startswith(url)]

links = get_links("http://b.hatena.ne.jp/hotentry")
links = [l for l in links if urlparse(l[0])[0]][3:]
for url, title in links[:10]:
    print "[%s:title=%s]" % (url, title.decode("utf-8", "replace"))
テキストエディタでWebサイト構築をガンバル人へ(1/3) − @IT
日本の携帯を高くしている真犯人は
404 Blog Not Found:38歳までに知ることになる、22歳の自分に教えてあげたいたった1つのこと
ウェブ制作・プログラマー・デザイナーのためのチートシート集 | コリス
 やる夫がはてなブックマークを始めたようです。 - 朱雀式
2015年、テレビは「ニコ動」化する?――NRIが示す未来像 (1/2) - ITmedia News
パソコン好きが青色申告を体験してみると?:第1回 まずは税金ってナニ? (1/5) - ITmedia Biz.ID
らばQ : 42歳までに知ることになる、22歳の自分に教えてあげたい12のこと
「見て欲しい」の本質忘れるな--吉本が語るネット時代の権利者像:コラム - CNET Japan
「真のゆとり教育」が生んだ18歳天才プログラマー トレンド-インタビュー:IT-PLUS

2008-02-04

[]重複する要素を取り除く

>>> xs = [5, 8, 5, 1, 1, 4, 2, 4, 3, 2]
>>> set(xs)
set([1, 2, 3, 4, 5, 8])
>>> sorted(set(xs), key=xs.index) # 順序を維持
[5, 8, 1, 4, 2, 3]

[]文字列を逆順にする

>>> str = "abcdefg"
>>> str[::-1]
'gfedcba'
>>>
>>> str[0:5:2]    # 0番目から5番目までを2つおきに取り出す
'ace'
>>> str[-1:-5:-1] # 後ろの4つの要素を逆順に取り出す
'gfed'

[]整数を漢数字に変換

#!python
# vim:fileencoding=utf-8

def num2kanji(num):
    KNUM = [u"", u"", u"", u"", u"", u"", 
            u"", u"", u"", u""]
    DIGIT1 = (u"", u"", u"", u"")
    DIGIT2 = (u"", u"", u"", u"", u"")
    
    try:
        num = int(num)
    except ValueError:
        raise ValueError("not an integer")
    max = 10000 ** len(DIGIT2) - 1
    if not(0 <= num < max):
        raise ValueError("not in (0-%d)" % max)

    if num == 0: return u""

    str_num = str(num)
    knum = []
    for i in xrange(((len(str_num) + 3) / 4)):
        sn = str_num[-1-i*4:-5-i*4:-1]
        if sn != "0000": 
            knum.append(DIGIT2[i] + " ")
            for j, n in enumerate(map(int, sn)):
                if n != 0:
                    knum.append(DIGIT1[j])
                    if not(n == 1 and j):
                        knum.append(KNUM[n])
    knum.reverse()
    return "".join(knum).rstrip()

while 1:
    try:
        print num2kanji(raw_input(">> "))
    except ValueError, e:
        print e
    except EOFError:
        break
>> 0
零
>> 1540001
百五十四万 一
>> 43005421003
四百三十億 五百四十二万 千三
>> 224767477905006
二百二十四兆 七千六百七十四億 七千七百九十万 五千六
>> 60093000611220000769
六千九京 三千兆 六千百十二億 二千万 七百六十九
>> -1
not in (0-99999999999999999999)
>> 1000000000000000000000000000
not in (0-99999999999999999999)
>> 89.97
not an integer

2008-02-01

[]wgetYouTube等から動画を落とす

今度は正規表現を使わずに書いてみました。

#!python
#encoding=utf-8

import urllib
import urllib2
import re
import os
import sys
import time

save_dir = r"c:\My Documents"
interval = 3


def get_video_detail(url):
    for host, video in VIDEOS.items():
        if url.find(host) != -1:
            return video.get_detail(url)

def _extract_from_to(str, from_, to_=None, to_end=False):
    start = str.find(from_)
    if start != -1:
        start += len(from_)
        end = None
        if to_:
            end = str.find(to_, start)
            if end == -1 and to_end:
                end = None
        if end != -1:
            return str[start:end]

VIDEOS = {}
class Video:
    def __init__(self, id_from_to, dl_url_from_to, title_from_to=None,
            api_url=None, encoding="utf-8", ext=".flv"):
        self.id_from_to = id_from_to
        self.dl_url_from_to = dl_url_from_to
        self.title_from_to = title_from_to
        self.api_url = api_url
        self.encoding = encoding
        self.ext = ext
    
    def get_detail(self, url):
        id = self._extract_id(url)
        if self.api_url:
            url = self.api_url % id
        content = self._get_content(url)
        params = self._extract_dl_url_params(content)
        dl_url = self._build_dl_url(params)
        title = self._extract_title(content)
        return id, dl_url, title, self.ext

    def _extract_id(self, url):
        from_, to_ = self.id_from_to
        id = _extract_from_to(url.lower(), from_, to_, True)
        if id:
            return id
        else:
            raise ValueError("invalid video url")
    
    def _get_content(self, url):
        try:
            response = urllib2.urlopen(url)
            return response.read()
        except urllib2.URLError:
            raise RuntimeError("unable to download video page")

    def _extract_dl_url_params(self, content):
        params = []
        for from_, to_ in self.dl_url_from_to:
            p = _extract_from_to(content, from_, to_)
            if p:
                params.append(p)
            else:
                raise RuntimeError("unable to extract download url")
        return params
    
    def _build_dl_url(self, params):
        return params[0]

    def _extract_title(self, content):
        if self.title_from_to:
            from_, to_ = self.title_from_to
            title = _extract_from_to(content, from_, to_)
            if title:
                return title.decode(self.encoding, "ignore")


class YouTube(Video):
    def __init__(self):
        Video.__init__(self,
            id_from_to = ("/watch?v=", "&"),
            dl_url_from_to = [("video_id=", "&"), ("&t=", "&")],
            title_from_to = ("<title>YouTube - ", "</title>")
        )
    def _build_dl_url(self, params):
        return "http://www.youtube.com/get_video?video_id=%s&t=%s" %\
               tuple(params)

VIDEOS["youtube.com"] = YouTube()


class Veoh(Video):
    def __init__(self):
        Video.__init__(self,
            id_from_to = ("/videos/", "?"),
            dl_url_from_to = [('fullPreviewHashPath="', '"')],
            api_url = "http://www.veoh.com/rest/video/%s/details",
            title_from_to = ('\ttitle="', '"')
        )
VIDEOS["www.veoh.com"] = Veoh()


class Dailymotion(Video):
    def __init__(self):
        Video.__init__(self,
            id_from_to = ("/video/", None),
            dl_url_from_to = [("&url=", "&")],
            title_from_to = ('<h1 class="nav with_uptitle">', "</h1>")
        )
    def _build_dl_url(self, params):
        return urllib.unquote(params[0])

VIDEOS["dailymotion.com"] = Dailymotion()


class AmebaVision(Video):
    def __init__(self):
        Video.__init__(self,
            id_from_to = ("movie=", None),
            dl_url_from_to = [("<imageUrlLarge>", "</imageUrlLarge>")],
            api_url = "http://vision.ameba.jp/api/get/detailMovie.do?movie=%s",
            title_from_to = ("\t<title>", "</title>")
        )
    def _build_dl_url(self, params):
        dl_url = params[0].replace("//vi", "//vm")
        dl_url = dl_url.replace("/jpg/", "/flv/")
        dl_url = dl_url.replace("_4.jpg", ".flv")
        return dl_url

VIDEOS["vision.ameba.jp"] = AmebaVision()


class Yourfilehost(Video):
    def __init__(self):
        Video.__init__(self,
            id_from_to = ("cat=video&file=", None),
            dl_url_from_to = [("&videoembed_id=", "&")]
        )
    def _extract_id(self, url):
        id = Video._extract_id(self, url)
        return os.path.splitext(id)[0]

    def _build_dl_url(self, params):
        return urllib.unquote(params[0])
    
VIDEOS["www.yourfilehost.com"] = Yourfilehost()


invalid_chr_re = re.compile(u'[\/:*?"<>|]')
for url in sys.argv[1:]:
    try:
        id, dl_url, title, ext = get_video_detail(url)
        filename = title or id
        filename = invalid_chr_re.sub(" ", filename)
        filepath = os.path.join(save_dir, filename + ext)
        command = "wget -O '%s' --referer='%s' '%s'" %\
                  (filepath, url, dl_url)
        os.system(command)
        time.sleep(interval)
    except (ValueError, RuntimeError), e:
        print "Error: %s :%s" % (e, url)

2008-01-29

[]wgetYouTube等から動画を落とす

Rubyに戻ってみました。selfを書かなくて済むのはいいんですが、Pythonに慣れてきたせいか、endを書くのが少し面倒になってます。後、インスタンスごとにメソッドの振る舞いを変えられるのはいいです。新しくクラスを作らなくて済みますし。

#!ruby

require 'net/http'
require 'cgi'
require 'kconv'

save_dir = 'c:/My Documents'
interval = 3

module Video
  class Error < StandardError; end
  class InvalidUrlError < Error; end

  VideoDetail = Struct.new(:id, :dl_url, :title, :ext, :encoding)

  def get_detail(url)
    @@videos.each do |video|
      begin
        return video.get_detail(url)
      rescue InvalidUrlError
        raise if video === @@videos.last
      end
    end
  end
  module_function :get_detail

  class Video

    def initialize(url_re, dl_url_re, title_re=nil, api_url=nil,
                   ext=".flv", encoding=Kconv::UTF8)
      @url_re = url_re
      @dl_url_re = dl_url_re
      @title_re = title_re
      @api_url = api_url
      @ext = ext
      @encoding = encoding
    end

    def get_detail(url)
      id = extract_video_id(url)
      if @api_url
        video_url = @api_url % id
      else
        video_url = url
      end
      content = get_content(video_url)
      params = extract_dl_url_params(content)
      dl_url = build_dl_url(params)
      title = extract_title(content)
      VideoDetail.new(id, dl_url, title, @ext, @encoding)
    end

    private

    def extract_video_id(url)
      @url_re.match(url).to_a[1] or
        raise InvalidUrlError.new('invalid video url')
    end

    def get_content(url)
      begin
        res = Net::HTTP.get_response(URI.parse(url))
        raise unless Net::HTTPSuccess === res      
        res.body
      rescue
        raise Error.new('unable to download video page')
      end
    end

    def extract_dl_url_params(content)
      @dl_url_re.match(content).to_a[1..-1] or
        raise Error.new('unable to extract download url params')
    end

    def build_dl_url(params)
      params.first
    end

    def extract_title(content)
      @title_re.match(content).to_a[1] if @title_re
    end

  end

  @@videos = []

  youtube = Video.new(
    %r!\Ahttp://(?:\w+\.)youtube\.com/watch\?v=([\w-]+)!,
    %r!watch_fullscreen\?.*?video_id=([\w-]+).*?&t=([\w-]+)!,
    %r!<title>YouTube - ([^<>]*)</title>!
  )
  def youtube.build_dl_url(params)
    "http://www.youtube.com/get_video?video_id=%s&t=%s" % params
  end
  @@videos << youtube

  veoh = Video.new(
    %r!\Ahttp://www\.veoh\.com/videos/(\w+)!,
    %r!fullPreviewHashPath="([^"]+)"!,
    %r!title="([^"]*)"\s+dateAdded=!,
    'http://www.veoh.com/rest/video/%s/details'
  )
  @@videos << veoh

  dailymotion = Video.new(
    %r!http://www.dailymotion\.com/.*?/video/([\w/-]+)!,
    %r!(http%3A%2F%2Fwww\.dailymotion\.com%2Fget%2F\d{2}%2F320x240%2Fflv%2F\d+\.flv%3Fkey%3D\w+)!,
    %r!<h1 class="nav with_uptitle">([^<>]*)</h1>!
  )
  def dailymotion.build_dl_url(params)
    CGI.unescape(params.first)
  end
  @@videos << dailymotion

  amebavision = Video.new(
    %r!http://vision\.ameba\.jp/watch\.do.*?\?movie=(\d+)!,
    %r!<imageUrlLarge>([^<>]+)</imageUrlLarge>!,
    %r!<item>\s*<title>([^<>]*)</title>!,
    "http://vision.ameba.jp/api/get/detailMovie.do?movie=%s"
  )
  def amebavision.build_dl_url(params)
    flv_url = params.first
    flv_url['//vi'] = '//vm'
    flv_url['/jpg/'] = '/flv/'
    flv_url['_4.jpg'] = '.flv'
    flv_url
  end
  @@videos << amebavision

  yourfilehost = Video.new(
    %r!http://(?:www\.)?yourfilehost\.com/media\.php\?cat=video&file=([\w.-]+)\.!,
    %r!videoembed_id=([\w%.-]+)&!
  )
  def yourfilehost.build_dl_url(params)
    CGI.unescape(params.first)
  end
  @@videos << yourfilehost
end

ARGV.each do |url|
  begin
    video = Video.get_detail(url)
  rescue  => e
    puts "Error: #{e} (#{url})"
    next
  end
  if video.title
    filename = video.title.kconv(Kconv::SJIS, video.encoding)
  else
    filename = video.id
  end
  filepath = File.join(save_dir, filename << video.ext)
  system('wget', '-O', filepath, "--referer=#{url}", video.dl_url)
  sleep interval
end