Web::Scraper in Python (もしくは scrAPI in Python)
lxml2.0からCSSSelectorが導入されたので、Web::Scraperのようなものを作ってみました。
とりあえず動作するところまでいったので載せておきます。機能はまだ全然足りてないので、簡単なことしかできません。
Python2.5とlxml2.0alpha が必要です。
Pythonはリストや辞書の中にある日本語をそのままprintで表示できないようなので、めんどくさいことに全部stringにして出力してます。
Web::Scraper - naoyaのはてなダイアリーよりキーワードページから必要なデータをもってくる例。
#!/usr/bin/env python2.5 from scraper import scraper, process import codecs, sys sys.stdout = codecs.getwriter('utf-8')(sys.stdout) s = scraper( process('span.title > a:first-child', title='TEXT', url='@href'), process('span.furigana', furigana='TEXT'), process('ul.list-circle > li:first-child > a', category='TEXT'), ) result = s.scrape('http://d.hatena.ne.jp/keyword/%BA%B0%CC%EE%A4%A2%A4%B5%C8%FE') print ''.join(result['category']) print ''.join(result['furigana']) print ''.join(result['title']) print ''.join(result['url'])
$ ./keyword.py アイドル こんのあさみ 紺野あさ美 /keyword/%ba%b0%cc%ee%a4%a2%a4%b5%c8%fe
Web::ScraperよりFlickrからサムネイルURLをとってくる例。
#!/usr/bin/env python2.5 from scraper import scraper, process import codecs, sys sys.stdout = codecs.getwriter('utf-8')(sys.stdout) s = scraper( process('a.image_link img', thumbs="@src"), ) result = s.scrape('http://www.flickr.com/photos/bulknews/sets/72157601700510359/') print "\n".join(result['thumbs'])
scrape.py自体はこんな感じ
#!/usr/bin/env python2.5 # -*- coding: utf-8 -*- from urllib import urlopen from lxml import etree def scraper(*funcs): class Scraper(object): def __init__(self, funcs): self.funcs = funcs def scrape(self, url): from StringIO import StringIO stash = {} res = urlopen(url) html = res.read().decode(res.headers.getparam('charset') or 'latin-1') tree = etree.parse(StringIO(html), etree.HTMLParser()) for f in self.funcs: xpath, attr = f() for key, val in attr.iteritems(): if val.startswith('@'): stash[key] = [e.attrib[val[1:]] for e in tree.xpath(xpath)] elif val.upper() == "TEXT": stash[key] = [e.text for e in tree.xpath(xpath)] else: print "Got an unknown thingy: ", what return stash return Scraper(funcs) def create_process(func): def do(selector, **kwargs): def wrap(): return func(selector, kwargs) return wrap return do @create_process def process(selector, kwargs): from lxml.cssselect import CSSSelector xpath = selector if selector.startswith('/') else CSSSelector(selector).path return xpath, kwargs