Engineer in Tokyo

feedparserで、media コンテンツを取る

feedparserで、どうやってビデオを取れるかをずっと悩みましたけど、今日少しだけ、進展した。問題の核心はyoutubeや、vimeoは Yahoo! RSS モジュールを使って、RSS拡張ネームスペースにデータを入れている。この拡張データの処理はfeedparserが 中途半端でやってる。見てみよう。

YoutubeのGDATA APIで取ったデータはこうなる

<entry>
  <id>http://gdata.youtube.com/feeds/api/videos/R3orQKBxiEg</id>
  <published>2008-07-17T23:58:51.000Z</published>
  <updated>2008-11-24T02:32:25.000Z</updated>
  ...
  <title type="text">Official Watchmen Trailer</title>
  <content type="text">Title speaks for itself

so people don't have to keep answerin
the name of the song is:

smashing pumpkins- the beginning is the end is the beginning</content>
    ...
  <author>
    <name>Garrettheparrot</name>
    <uri>http://gdata.youtube.com/feeds/api/users/garrettheparrot</uri>
  </author>
  <media:group>
    <media:title type="plain">Official Watchmen Trailer</media:title>
    <media:description type="plain">Title speaks for itself

so people don't have to keep answerin
the name of the song is:

smashing pumpkins- the beginning is the end is the beginning</media:description>
    <media:keywords>2009, Comic, Men, Movie, Watch, Watches, Who</media:keywords>
    <yt:duration seconds="140"/>
    <media:category label="Film &amp; Animation" scheme="http://gdata.youtube.com/schemas/2007/categories.cat">Film</media:category>
    <media:content url="http://www.youtube.com/v/R3orQKBxiEg&amp;f=gdata_user_favorites" type="application/x-shockwave-flash" medium="video" isDefault="true" expression="full" duration="140" yt:format="5"/>
    <media:content url="rtsp://rtsp2.youtube.com/CjALENy73wIaJwlIiHGgQCt6RxMYDSANFEgGUhRnZGF0YV91c2VyX2Zhdm9yaXRlcww=/0/0/0/video.3gp" type="video/3gpp" medium="video" expression="full" duration="140" yt:format="1"/>
    <media:content url="rtsp://rtsp2.youtube.com/CjALENy73wIaJwlIiHGgQCt6RxMYESARFEgGUhRnZGF0YV91c2VyX2Zhdm9yaXRlcww=/0/0/0/video.3gp" type="video/3gpp" medium="video" expression="full" duration="140" yt:format="6"/>
    <media:thumbnail url="http://i.ytimg.com/vi/R3orQKBxiEg/2.jpg" height="97" width="130" time="00:01:10"/>
    <media:thumbnail url="http://i.ytimg.com/vi/R3orQKBxiEg/1.jpg" height="97" width="130" time="00:00:35"/>
    <media:thumbnail url="http://i.ytimg.com/vi/R3orQKBxiEg/3.jpg" height="97" width="130" time="00:01:45"/>
    <media:thumbnail url="http://i.ytimg.com/vi/R3orQKBxiEg/0.jpg" height="240" width="320" time="00:01:10"/>
    <media:player url="http://www.youtube.com/watch?v=R3orQKBxiEg"/>
  </media:group>
  ...
</entry>

media という名前空間の下に結構データが入ってる。なのに、feedparserはちょっとしか取らない。

>>> d = feedparser.parse("http://gdata.youtube.com/feeds/api/users/IanLewisInJapan/favorites")
>>> e = d['entries'][0]
>>> filter(lambda x: x.startswith('media_'), e.keys())
['media_category', 'media_player', 'media_group', 'media_keywords', 'media_description', 'media_content', 'media_thumbnail']
>>> e['media_content']
u''
>>> e['media_thumbnail']
u''
>>> e['media_player']
u''
>>> e['media_description']
u'Last.fm/presents Yellow Magic Orchestra Interview at Royal Festival Hall in London.\nCheck out http://www.last.fm/Presents to find out about all of our other interviews or upcoming/past events.'
>>>

ビデオのURLはどこかにない。原因はfeedparserの拡張ネームスペース処理に入ってるけども、一言いうと、 <media:content>というタグの属性は取れてない。こう見たら、どうやって、とれるかを調べたら、 unknown_starttag()というメソッドの中にこのコードを見つけた。

feedparser.py

# call special handler (if defined) or default handler
methodname = '_start_' + prefix + suffix
try:
  method = getattr(self, methodname)
  return method(attrsD)
except AttributeError:
  return self.push(prefix + suffix, 1)

じゃ、XMLを解析するときに、タグを見つけたら、unknown_starttag()という関数が実行されて、 タグの名前に一致するメソッドがあれば、実行する処理やってる。それで、start_media_content()というメソッドがあれば実行してくれるわけだね。 でも、どうやって、パーサークラスに付けるのか。feedparserは_StrictFeedParserというクラスを名前で使ってるから、自分が作ったクラスを_ScrictFeedParserと交換。

feedparser._StrictFeedParser_old = feedparser._StrictFeedParser
class DlifeFeedParser(feedparser._StrictFeedParser_old):

  def _start_media_content(self, attrsD):
    self.entries[-1]['media_content_attrs'] = copy.deepcopy(attrsD)
feedparser._StrictFeedParser = DlifeFeedParser

それで、またparse()を実行すると、

>>> import feedparser
>>> import copy
>>> feedparser._StrictFeedParser_old = feedparser._StrictFeedParser
>>> class DlifeFeedParser(feedparser._StrictFeedParser_old):
...
...   def _start_media_content(self, attrsD):
...         self.entries[-1]['media_content_attrs'] = copy.deepcopy(attrsD)
...     return self.push('media_content', 1)
...
>>> feedparser._StrictFeedParser = DlifeFeedParser
>>> d = feedparser.parse("http://gdata.youtube.com/feeds/api/users/IanLewisInJapan/favorites")
>>> filter(lambda x: x.startswith("media_"), d['entries'][0].keys())
['media_category', 'media_player', 'media_group', 'media_content_attrs', 'media_keywords', 'media_description', 'media_content', 'media_thumbnail']
>>> d['entries'][0]['media_content_attrs']
['medium', 'format', 'url', 'expression', 'duration', 'type', 'yt:format']

それで、media:contentの属性を取れた。media:groupの下にcontentが複数ある場合もあるから、 もうちょっとまとめないといけないけど、やり方が少し分かってきた。