忍者ブログ
     2008年11月14日 開始
×

[PR]上記の広告は3ヶ月以上新規記事投稿のないブログに表示されています。新しい記事を書く事で広告が消えます。

http://www.python.jp/Zope/articles/tips/regex_howto/regex_howto_4

4.3. 取り出さないグループと名前付きグループ

凝った正規表現を書こうとすると、文字列を取り出すためのグループと、正規表現自体を構造化するためのグループを使う場合があります。しかし、複雑な正規表現では、グループ番号を追いかけ続けるのが困難になります。この問題を解決するのに 2 つの方法があります。両方とも正規表現で拡張された、一般的な文法を使うので、まずそれを見ていきましょう。

Perl 5 では、標準の正規表現の他にいくつかの機能が加えられ、Python の re モジュールはそのほとんどをサポートしています。 Perl の正規表現と標準の正規表現が、混乱するような方法で違ったものにならないように、1 文字で入力できる新しいメタ文字や、新しい機能を表すために "\" で始まる特殊なシーケンスを選ぶことは難しかったものです。たとえば、"&" を新しいメタ文字にするとしましょう。ところが古い表記では "&" が通常の文字なので、\&[&] のようにエスケープされていないわけです。選ばれた解決策は、(?...) を拡張文法として使うことでした。括弧直後の "?" は繰り返す対象がないので、シンタックスエラーです。ですから、これは互換性の問題を発生しません。 "?" 直後の文字で、どの拡張が使うかを示すので、(?=foo)(?:foo) は別のものです(それぞれ a positive lookahead assertion と non-capturing group containing the subexpression foo)。

Python では、 Perl の拡張文法に、さらに拡張文法を追加されています。 Python で拡張されたものは、疑問符直後の最初の文字が "P" になっています。現在のところ、そのような拡張は 2 つあります。 (?P<name>...) は名前つきグループを定義し、(?P=name) は名前つきグループを後方参照します。将来 Perl 5 が似たような機能を別の文法で追加すれば、Python 独自の文法は互換性のために残しつつ、re は、Perl の文法をサポートする変更されるでしょう。

ここまで見てきた一般的な拡張文法を踏まえて、複雑な正規表現中のグループを扱うことを単純にする機能に話を戻しましょう。グループは左から右へと番号づけされ、複雑な正規表現では多くのグループを使います。したがって、正しい番号を追いかけるのが難しくなり、そのような複雑な正規表現を修正するのはやっかいです。先頭付近に新しいグループを挿入すれば、それ以降の全ての番号を変更することになるからです。

グループの内容を取得しないけれども、正規表現の一部を集めるために、グループを使いたい場合があります。これを明示するには、非取得グループを示す (?:...) を使います。括弧内にどんな正規表現でも配置できます。

>>> m = re.match("([abc])+", "abc")
>>> m.groups()
('c',)
>>> m = re.match("(?:[abc])+", "abc")
>>> m.groups()
()

マッチしたグループの内容を取得できないという事実を除けば、非取得グループは内容を取得できるグループとまったく同じように振舞います。この中に何か別の表現を入れたり、"*" 等の繰り返しメタ文字で繰り返したり、他のグループ (取得でも非取得でも) の入れ子にしたりすることもできます。また、他のグループの番号づけを変更することなく、新しいグループを追加できるので、既に存在するグループを修正するときに (?:...) は特に役に立ちます。グループの内容を取得してもしなくても、探索の性能に違いはありません。どちらも同じ速さで動作します。

より重要な機能は名前付きグループです。グループを番号で参照するのではなく、名前で参照できるのです。

名前付きグループの文法は Python の独自拡張のひとつで、(?P<name>...) と表記します。 name は、もちろん、グループの名前です。グループを名前で関連づけることを除けば、名前付きグループは取得グループと同じように振る舞います。取得グループを扱う、全ての MatchObject のメソッドは、グループに付けられた番号を参照するために整数か、グループ名を受け付けます。名前付きグループは番号付けもされているので、2 通りの方法で、グループの情報を取得することができるのです。

>>> p = re.compile(r'(?P<word>\b\w+\b)')
>>> m = p.search( '(((( Lots of punctuation )))' )
>>> m.group('word')
'Lots'
>>> m.group(1)
'Lots'

名前付きグループを使えば、番号を覚える代わりに、覚えやすい名前で参照できるので便利です。 imaplib モジュールから、正規表現の例を示します。

InternalDate = re.compile(r'INTERNALDATE "'
        r'(?P<day>[ 123][0-9])-(?P<mon>[A-Z][a-z][a-z])-'
        r'(?P<year>[0-9][0-9][0-9][0-9])'
        r' (?P<hour>[0-9][0-9]):(?P<min>[0-9][0-9]):(?P<sec>[0-9][0-9])'
        r' (?P<zonen>[-+])(?P<zoneh>[0-9][0-9])(?P<zonem>[0-9][0-9])'
        r'"')

m.group('zonem') を取得するほうが、グループ番号 9 を覚えるより、とても簡単です。

(...)\1 のような表記では、後方参照のための文法がグループ番号を参照するので、名前を使う方法ではと自然と不一致が生じます。そこで、Python 独自拡張には (?P=name) があります。これは、name と名付けられたグループの内容が、現在位置に存在することを示します。繰り返される単語を見付ける正規表現、(\b\w+)\s+\1 は、(?P<word>\b\w+)\s+(?P=word) と書くこともできるのです。

>>> p = re.compile(r'(?P<word>\b\w+)\s+(?P=word)')
>>> p.search('Paris in the the spring').group()
'the the'
PR

みんなのPython(Webアプリ編を読んでいる途中・・・)

Webサーバーを作ったよ

cgiserver.py

import CGIHTTPServer
CGIHTTPServer.test()

真値テストを行うことができます。 以下の値は偽であると見なされます:

①None ②False

③数値型におけるゼロ。例えば 0 、 0L 、 0.0 、 0j

④空のシーケンス型。例えば '' 、 () 、 [] 。

⑤空のマッピング型。例えば {} 。

⑥__nonzero__() または __len__() メソッドが 定義されているようなユーザ定義クラスのインスタンスで、それらのメソッド が整数値ゼロまたは bool 値の False を返すとき。 6


それ以外の値は全て真であると見なされます -- 従って、ほとんどの型 のオブジェクトは常に真です。

3 range() 関数

この関数は算術型の数列が入ったリストを生成します。

>>> range(10)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

指定した終端値は生成されるリストには入りません。

range(10) は 10 個の値からなるリストを生成し、 長さ 10 のシーケンスにおける各項目のインデクスとなります。 range を別の数から開始したり、他の増加量 (負の増加量でさえも; 増加量は時に `ステップ(step)' と呼ばれることもあります) を指定する こともできます:

>>> range(5, 10)
[5, 6, 7, 8, 9]
>>> range(0, 10, 3)
[0, 3, 6, 9]
>>> range(-10, -100, -30)
[-10, -40, -70]

 

5.2.4 リスト表現

リスト表現は、角括弧で囲われた式の系列です。系列は空の系列であってもかまいません:

 

test ::= or_test | lambda_form
testlist ::= test ( "," test )* [ "," ]
list_display ::= "[" [listmaker] "]"
listmaker ::= expression ( list_for | ( "," expression )* [","] )
list_iter ::= list_for | list_if
list_for ::= "for" expression_list "in" testlist [list_iter]
list_if ::= "if" test [list_iter]
Download entire grammar as text.

リスト表現は、新に作成されたリストオブジェクトを表します。新たなリストの内容は、式のリストを与えるか、リストの内包表記 (list comprehension) で指定します。 カンマで区切られた式のリストを与えた場合、リストの各要素は左から右へと順に評価され、評価された順番にリスト内に配置されます。リストの内包表記を与える場合、内包表記はまず単一の式、続いて少なくとも一つの for 節、続いてゼロ個以上の for 節か、if 節になります。この場合、新たに作成されるリストの各要素は、各々の forif 節を左から右の順にネストしたブロックとみなして実行し、ネストの最内ブロックに到達する度に式を評価した値となります。 5.1

5.3 タプルとシーケンス

リストや文字列には、インデクスやスライスを使った演算のように、数多くの共通の性質があることを見てきました。これらは シーケンス (sequence) データ型 の二つの例です。Python はまだ進歩の過程にある言語なので、他のシーケンスデータ型が追加されるかもしれません。標準のシーケンス型はもう一つあります: タプル (tuple) 型です。

タプルはコンマで区切られたいくつかの値からなります。例えば以下のように書きます:

 

>>> t = 12345, 54321, 'hello!'
>>> t[0]
12345
>>> t
(12345, 54321, 'hello!')
>>> # タプルを入れ子にしてもよい
... u = t, (1, 2, 3, 4, 5)
>>> u
((12345, 54321, 'hello!'), (1, 2, 3, 4, 5))

ご覧のように、タプルは常に丸括弧で囲われています。これは、入れ子になったタプルが正しく解釈されるようにするためです; 入力の際には丸括弧なしでもかまいませんが、結局 (タプルがより大きな式の一部分の場合) たいてい必要となります。

タプルの用途はたくさんあります。例えば、(x, y) 座標対、データベースから取り出した従業員レコードなどです。タプルは文字列と同じく、変更不能です: タプルの個々の要素に代入を行うことはできません (スライスと連結を使って同じ効果を実現することはできますが)。リストのような変更可能なオブジェクトの入ったタプルを作成することもできます。

問題は 0 個または 1 個の項目からなるタプルの構築です: これらの操作を行うため、構文には特別な細工がされています。空のタプルは空の丸括弧ペアで構築できます; 一つの要素を持つタプルは、値の後ろにコンマを続ける (単一の値を丸括弧で囲むだけでは不十分です) ことで構築できます。美しくはないけれども、効果的です。例えば以下のようにします:

 

>>> empty = ()
>>> singleton = 'hello',    # <-- 末尾のコンマに注目
>>> len(empty)
0
>>> len(singleton)
1
>>> singleton
('hello',)

t = 12345, 54321, 'hello!'タプルのパック (tuple packing) の例です: 値 1234554321 、および 'hello!' が一つのタプルにパックされます。逆の演算も可能です:

 

>>> x, y, z = t

この操作は、シーケンスのアンパック (sequence unpacking) とでも呼ぶべきものです。シーケンスのアンパックでは、左辺に列挙されている変数が、右辺のシーケンスの長さと同じであることが要求されます。複数同時の代入が実はタプルのパックとシーケンスのアンパックを組み合わせたものに過ぎないことに注意してください!

この操作にはわずかな非対称性があります: 複数の値をパックすると常にタプルが生成されますが、アンパックはどのシーケンスにも働きます。

 


 

zip( [iterable, ...])
この関数はタプルのリストを返します。このリストの i 番目のタプルは各引数のシーケンスまたはイテレート可能オブジェクト中の i 番目の要素を含みます。返されるリストは引数のシーケンスのうち長さが最小のものの長さに切り詰められます。引数が全て同じ長さの際には、 zip() は初期値引数が Nonemap() と似ています。引数が単一のシーケンスの場合、1 要素のタプルからなるリストを返します。引数を指定しない場合、空のリストを返します。 バージョン 2.0 で 新たに追加 された仕様です。

バージョン 2.4 で 変更 された仕様: これまでは、zip() は少なくとも一つの引数を要求しており、空のリストを返す代わりに TypeError を送出していました

 


 

 __init__ はインスタンスを作った直後に呼ばれる特殊メソッドです(省略可能だが、実質的なコンストラクター)。

特殊メソッド

Pythonのクラスには tex2html_wrap_inline573 のように予め決められた特殊メソッドがあります。その一部を表 1にしました。 tex2html_wrap_inline583 はインタラクティブモードでオブジェクトをダイレクトに入力された時に呼び出されます。 tex2html_wrap_inline585 はオブジェクトが文字列化された時に呼び出されます。これらを使えば、オブジェクトの表示をカスタマイズして分かりやすくする事が出来ます。

tex2html_wrap_inline587 は他のオブジェクトとの比較を行ったときに呼び出されるメソッドで自分自身の参照の他に比較の相手のオブジェクトの参照が引数として渡されます。

tex2html_wrap_inline589tex2html_wrap_inline591 はメンバーの参照やセットが行われた時に呼び出されます。PythonにはC++のようにメンバーのアクセス制限が出来ないのですが、このメソッドを使えばそれに近いことが出来るかも知れません。

 

   table140
Table 1: 特殊メソッド(一部)
 

#import urllib
import urllib2
import urlparse
import BeautifulSoup

url = 'http://mixi.jp'
url='http://hiw.oo.kawai-juku.ac.jp/nyushi/honshi/08/'
url='http://hiw.oo.kawai-juku.ac.jp/nyushi/honshi/07/t01.html'
soup = BeautifulSoup.BeautifulSoup(urllib2.urlopen(url))
base=url
#縲€url縺ョ蜿門セ・
for _a in soup.findAll('a'):
    url_fragments=_a.get('href')
    print 'url_fragments = ' + str(_a.get('href'))
    print '      urljoin = ' + urlparse.urljoin(base, url_fragments)

# 繝輔Ξ繝シ繝縺ョ蜿門セ・
for _a in soup.findAll('frame'):
    url_fragments=_a.get('src')
    print 'url_fragments = ' + str(_a.get('src'))
    print '      urljoin = ' + urlparse.urljoin(base, url_fragments)

===以下は訂正版===

# -*- coding: utf-8 -*-

import urllib
import BeautifulSoup
#import urllib
import urllib2
import urlparse
import BeautifulSoup

url = 'http://mixi.jp'
url='http://hiw.oo.kawai-juku.ac.jp/nyushi/honshi/08/'
url='http://hiw.oo.kawai-juku.ac.jp/nyushi/honshi/07/t01.html'
soup = BeautifulSoup.BeautifulSoup(urllib2.urlopen(url))
base_url='http://hiw.oo.kawai-juku.ac.jp/nyushi/honshi/07/t01.html'

base_url='http://hiw.oo.kawai-juku.ac.jp/nyushi/honshi/07/'
#print soup
#print soup.prettify
#縲€url縺ョ蜿門セ・
for _a in soup.findAll('frame'):
    url_fragments=_a.get('src')
    print 'url_fragments =' , url_fragments

    print 'url_fragments = ' , str(url_fragments)
    print '      urljoin = ' + urlparse.urljoin(base_url, str(url_fragments))


http://www.yukun.info/blog/2008/07/python-string.html
 

  1. #!/usr/bin/python  
  2. # coding: UTF-8  
  3.  
  4. # 文字列の検索 | index(), reindex()の使い方  
  5.  
  6. s1 = 'Hello, Jan !' 
  7.  
  8. # 引数(パターン)が1文字の文字列  
  9. try:  
  10.     i = s1.index('l')  # 引数で与えられた文字列を先頭から探索した場合の出現位置を返す  
  11. except ValueError:  
  12.     i = None           # 存在しない場合は例外ValueErrorがなげれられる  
  13. print i, s1[i]  
  14.  
  15. try:  
  16.     i = s1.rindex('l'# 末尾から探索した場合の最初の出現位置を返す  
  17. except ValueError:  
  18.     i = None 
  19. print i, s1[i]  
  20. print 
  21.  
  22. # 引数(パターン)が2文字以上の文字列  
  23. str = 'lo' 
  24. try:  
  25.     i = s1.index(str)  
  26. except ValueError:  
  27.     i = None 
  28. print i, s1[i]  
  29.  
  30. try:  
  31.     i = s1.rindex(str)  
  32. except ValueError:  
  33.     i = None 
  34. print i, s1[i]  
  35. print 
  36.  
  37. # 引数で与えられた文字列(パターン)が存在しない場合  
  38. try:  
  39.     i = s1.index('Max')  
  40. except ValueError:  
  41.     i = None 
  42. print i 

実行結果

2 l
3 l

3 l
3 l

None

リファレンス

http://www.uselesspython.com/tutorials/BatteriesIncluded.php
より

 

>>> import calendar
>>> dir(calendar)

>>> import calendar
>>> for thingy in dir(calendar):
.......print thingy
 

 

BeautifulSoupでHTML解析

| | コメント(0) | トラックバック(0)

BeautifulSoupが素晴らしいので
他にあまり解説サイトもないし、
簡単に使えそうなものを羅列してみた。

ただしデフォルトエンコーディングを設定している事が前提。

BeautifulSoupに渡されたHTMLは、
utf-8に文字コードを変換され自動で綺麗に生成しなおされる。
prettify()を使用すると、綺麗なソースに生成しなおされる。
※BeautifulSoup内で処理される場合は、このソースが元になる。

壊れたタグを修復という訳ではなさそうだが
改行やインデントを作り直してくれる。
Webアプリケーションとしても、かなり使えそう。

# -*- coding: utf-8 -*-

import string, re, urllib, urllib2
from BeautifulSoup import BeautifulSoup

url = 'http://www.google.co.jp/search?hl=ja&num=100&q='
# url = 'http://search.yahoo.co.jp/search?p=' # Yahooの場合

# クライアントを生成
opener = urllib2.build_opener()

# PythonのUAがGoogleに弾かれているようなので変更
opener.addheaders = [('User-Agent', 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)')]

q = 'Perl使いのPythonちゃん' # 検索クエリ
q = urllib.quote(q) # クエリをURLエンコード

# Google検索結果を取得
print url+q
html = opener.open(url+q).read()

# HTMLソースをBeautifulSoupに渡す
soup = BeautifulSoup(html)

# エンコーディングを自動で取得
enc = soup.originalEncoding
print "Encoding : %s" % enc

# ソースを出力
# ※unicode(text, 'utf-8')はutf-8の文字をunicodeに変換
# print unicode(soup.prettify(), enc)

# ※UnicodeEncodeError: 'cp932' codec can't encode character...は、
# Windowsの拡張文字でエラーが起こっているのかも。
# その場合は'mbcs'というCodecを使う
#print unicode(soup.prettify(), enc).encode('mbcs')

### タグへのアクセス方法 - 1 (DOMもどきな扱い方)
print u'DOMもどきな扱い方'

# contents[0]で、その階層の1番上の要素にアクセス
# .name で、要素のタグ名を取得(小文字)
print soup.contents[0].name
# 結果はu'html'となる

# 更に子となる要素へアクセス(結果 : u'head')
print soup.contents[0].contents[0].name

# 要素を変数に格納
head = soup.contents[0].contents[0]

# 親要素にアクセス(結果 : u'html')
print head.parent.name

# 自分を含む、それ以下の階層の次の要素にアクセス(結果 : u'meta')
print head.next.name

# 同階層の次の要素にアクセス(結果 : u'body')
print head.nextSibling.name

# 複合(結果 : u'table')
print head.nextSibling.contents[0].name
# 複合(結果 : u'blockquote')
print head.nextSibling.contents[0].nextSibling.name

### タグへのアクセス方法 - 2 (よりDOMっぽい扱い方)

print u'DOMもどきな扱い方'

# タグ名でアクセス
titleTag = soup.html.head.title

# そのまま出力するとタグごと表示
print u"%s" % titleTag

# string でinnerText
print u"%s" % titleTag.string

# 要素数を取得
print len(soup('font'))

# 一致したタグの一番目を返す(属性で絞込みもできる)
soup.find('a')

# 一致したタグをリストにして返す(属性で絞込みもできる)
print soup.findAll('a', href="/")

# 正規表現さえ使える
print soup.find('a', href=re.compile('^/'))

# リストとしてアクセスも可能
print soup('a', href=re.compile('^/'))[1]

# 要素の値を返す
print soup('a', href=re.compile('^/'))[0]['href']

# 覚書 : xmlを解析する場合はBeautifulStoneSoup(xml)を使う?(未確認)

DOMのような感じで、置き換えたりタグを生成したりも
できるようだが、現状では必要ないのでまた今度。

ようし、飽きた!
これらを駆使してサンプルでも作ってみる。

トラックバック(0)

このブログ記事を参照しているブログ一覧: BeautifulSoupでHTML解析

このブログ記事に対するトラックバックURL: http://mt.blog-slime.com/mt-tb.cgi/8

コメントする

サインインしてください。 (匿名でコメントする)

このブログ記事について

このページは、adminが2007年7月27日 14:00に書いたブログ記事です。

ひとつ前のブログ記事は「PythonでHTML解析(HTMLParserより優れたパーサ)」です。

次のブログ記事は「Perlで言う、join関数が使い難い。」です。

最近のコンテンツはインデックスページで見られます。過去に書かれたものはアーカイブのページで見られます。

http://www.python.jp/Zope/articles/tips/regex_howto/regex_howto_3
 

3.1. 正規表現のコンパイル

正規表現は RegexObject インスタンスにコンパイルされます。このインスタンスは、パターン・マッチや文字列交換のような、様々な働きをするメソッドを持ちます。

>>> import re
>>> p = re.compile('ab*')
>>> print p
<re.RegexObject instance at 80b4150>

re.compile() に引数 flags を指定することで、特別な設定や文法の変更を実現するのに使われます。使用可能な設定は後で見ていくことにして、ここでは例だけ挙げておきます。

>>> p = re.compile('ab*', re.IGNORECASE)

正規表現は re.compile() に文字列として渡されます。これは、正規表現は Python 本体には含まれていないので、正規表現を表すのに特化した文法が用意されていないからです。 (正規表現を全く必要としないアプリケーションも存在するので、正規表現を内包することによる言語仕様をふくらませる必要はありません) その代わり re モジュールは、string モジュールと同様、単なる C 拡張モジュールになっています。

3.2. バックスラッシュだらけ

既に述べたように、正規表現はバックスラッシュ文字("\")で、特殊な形式を表したり、特殊文字に特殊な意味を持たせないのに使います(訳注: 日本語フォントセットでは、バックスラッシュは円記号で表示されます)。この規則は、文字列リテラル中で Python がバックスラッシュを同じ目的で使用するのと衝突します。

LaTeX 文書で見られるような文字列 "\section" とマッチする正規表現を書きたいとしましょう。プログラムのソースコードの中で、何と書くか導くために、マッチさせたい文字列から考えます。次にバックスラッシュを前置して、バックスラッシュやメタ文字をエスケープしなければいけません。その結果、文字列 "\\section" を得ます。 re.compile() に渡されるべき文字列は、\\section でなければいけません。しかし、Python の文字列リテラルでこれを表現するには、両方のバックスラッシュを再度エスケープしなければならないのです。

文字列 段階
\section マッチさせるテキスト文字列
\\section re.compile 用にバックスラッシュをエスケープ
"\\\\section" 文字列リテラル用にバックスラッシュをエスケープ

つまり、リテラルのバックスラッシュにマッチさせるには、正規表現文字列として '\\\\' と書かなければいけません。なぜなら正規表現は "\\" でなくてはいけませんし、Python の文字列リテラル中で、各バックスラッシュは "\\" と書かれなければいけないからです。バックスラッシュが繰り返し現れる正規表現中では、これが多くのバックスラッシュの繰り返しを誘発することになり、その文字列を理解することが困難になります。

解決策は Python の生の文字列(raw string)表記を正規表現に使うことです。 "r" を前置した文字列リテラル内では、バックスラッシュは特別な扱いを受けません。ですから r"\n" は、"\" と "n"の 2 文字からなる文字列ですが、"\n" は 1 つの改行文字です。 Python のコードで正規表現を書くとき、この生の文字列表記がよく用いられます。

一般の文字列 生の文字列
"ab*" r"ab*"
"\\\\section" r"\\section"
"\\w+\\s+\\1" r"\w+\s+\1"

3.3. マッチングを行う

ここまでで、コンパイルされた正規表現を represent するオブジェクトを持っているのですが、これで何をすればいいのでしょう。 RegexObject インスタンスは、いくつかのメソッドと属性を持っています。最も重要なものだけをここで扱います。完全なリストを手に入れるには、ライブラリ・リファレンスをあたってください。

メソッド / 属性 目的
match 正規表現が、文字列の先頭でマッチするかどうか調べる
search 文字列を走査し、正規表現がマッチする位置を探す
split 正規表現がマッチする全ての位置で、文字列をリストに分割する
sub 正規表現がマッチする全ての部分文字列を見つけ出し、別の文字列に置換する
subn sub()と同じ。ただし置換の個数を制限することができる

全くマッチしなければ、これらのメソッドは None を返します。見つかれば MatchObject インスタンスを返します。このインスタンスは、マッチした部分文字列や、それがどこで始まり、どこで終わるか、などの情報をもっています。

re モジュールを使って、対話的に実験することで勉強していきましょう。 (Tkinter が使えるなら redemo.py を見るのもよいでしょう。これは Python ディストリビューションに含まれているデモです。正規表現を入力すると、マッチしたか失敗したかを表示してくれるのです。コンパイルされた正規表現をデバッグしようとするときには、redemo.py が非常に有用であると思われます)

最初に、Python インタプリタを起動して、re をインポートしてから、正規表現をコンパイルします。

Python 1.5.1 (#6, Jul 17 1998, 20:38:08)	[GCC 2.7.2.3] on linux2
Copyright 1991-1995 Stichting Mathematisch Centrum, Amsterdam
>>> import re
>>> p = re.compile('[a-z]+')
>>> p
<re.RegexObject instance at 80c3c28>

これで正規表現 [a-z]+ に対して、様々な文字列のマッチングを試みることができます。 + は「1 回または、それ以上の繰り返し」を意味するので、空の文字列はまったくマッチしないはずです。この場合 match() の戻り値 None はインタプリタに何の出力も表示させません。結果をはっきりさせたければ、明示的に match() の結果をを表示すこともできます。

>>> p.match("")
>>> print p.match("")
None

今度は、"tempo" のような、マッチする文字列を試してみましょう。この場合、match()MatchObject を返すので、後で使えるように、この結果を変数に保存しておきます。

>>> m = p.match('tempo')
>>> print m
<re.MatchObject instance at 80c4f68>

この MatchObject から、マッチした文字列に関する情報を得ることができます。 MatchObject インスタンスには、いくつかのメソッドと属性があります。そのうち特に重要なものは以下のとおりです。

メソッド / 属性 目的
group() 正規表現にマッチした文字列を返す
start() マッチした文字列が始まる位置を返す
end() マッチした文字列が終わる位置を返す
span() マッチした文字列の位置を (start, end) のタプルで返す

これらのメソッドを使ってみれば、どういうことか明確になるでしょう。

>>> m.group()
'tempo'
>>> m.start(), m.end()
(0, 5)
>>> m.span()
(0, 5)

group() は正規表現にマッチした部分文字列を返します。 start()end() は、マッチした部分の開始位置と終了位置を返します。 span() は開始位置と終了位置の両方を、タプルにして返します。 match メソッドは、正規表現が文字列の先頭でマッチするかどうかをチェックするだけなので、start() の戻り値は常に 0 です。しかし RegexObject インスタンスの search メソッドは、文字列全部に渡って調べるので、マッチの開始位置が 0 でない場合もあります。

>>> print p.match('::: message')
None
>>> m = p.search('::: message') ; print m
<re.MatchObject instance at 80c9650>
>>> m.group()
'message'
>>> m.span()
(4, 11)

実際のプログラムでは、MatchObject を変数に保存し、その値が None かどうかチェックするのが最も一般的なスタイルで、次のようなになります。

p = re.compile( ... )
m = p.match('string goes here')
if m:
    print 'Match found: ', m.group()
else:
    print 'No match'

3.4. モジュールレベルの関数

必ずしも RegexObjectを作ったり、そのメソッドを呼び出す必要はありません。 re モジュールは、match(), search(), sub(), などのトップレベル関数も提供します。これらの関数は、RegexObject メソッドと対応していて、第 1 引数が正規表現文字列であるような引数を取り、なおかつ None または MatchObject インスタンスを返します。

>>> print re.match(r'From\s+', 'Fromage amk')
None
>>> re.match(r'From\s+', 'From amk Thu May 14 19:12:10 1998')
<re.MatchObject instance at 80c5978>

この隠れ蓑の下で、これらの関数はあなたに代わって RegexObject を作り、それに対して適切なメソッドを呼び出すだけです。また、コンパイルされたオブジェクトをキャッシュに保存し、後で、同じ正規表現をを使うときの呼び出しが速くなります。

モジュールレベルの関数を使うべきでしょうか、RegexObject を作ってからメソッドを呼び出すべきでしょうか。どれだけ頻繁にその正規表現を使わうのか、あなたのコーディングスタイル次第です。もし正規表現がコードの中で、たった 1 箇所だけで使うのなら、モジュール関数がおそらく便利でしょう。一方、プログラムがたくさんの正規表現を包含していたり、同じ正規表現を別々の箇所で何度も使ったりするなら、全ての定義を始めの 1 箇所に集めて、正規表現をコンパイルする価値があるでしょう。標準ライブラリの xmllib.py を例にとってみましょう。

ref = re.compile( ... )
entityref = re.compile( ... )
charref = re.compile( ... )
starttagopen = re.compile( ... )

(私はふつう、1 回使うだけであっても、コンパイル済みオブジェクトを好みます。私と同じくらい、徹底するようになる人もいるようです)

3.5. コンパイル時のフラグ

コンパイル時にフラグを指定することで、正規表現が動作するときの振るまいを変更できます。 re モジュールのフラグには、IGNORECASE のようなロングネームと、Iのように1文字で表すショートネームが使えます。 (もし Perl のパターン修飾子に慣れているなら、ショートネームは Perl と同じ文字を使うと言えば分かりやすいかも知れません。 re.VERBOSE のショートネームは re.X です) 複数のフラグは、OR ビット演算で指定できます。たとえば、re.I | re.M は、IM の両方を指定することになります。

以下に使用できるフラグの表を示します。詳しい説明は後述します。

フラグ 意味
DOTALL, S . が改行も含めて、全ての文字とマッチするように指定する
IGNORECASE, I 大文字小文字を区別しない
LOCALE, L ロケールを考慮してマッチングを行う
MULTILINE, M 複数行にマッチング。これは ^$ に影響する
VERBOSE, X 冗長な正規表現(もっと、きれいで分かりやすくまとめられる表現)を有効にする。
I, IGNORECASE
大文字小文字を区別せずにマッチングを行う。文字クラスとリテラル文字列は大文字小文字を無視して、マッチングが行われる。例えば、[A-Z] は小文字にもマッチし、Spam は、"Spam"、"spam"、"spAM" にマッチします。このフラグ指定では、カレント・ロケールを考慮しません。
L, LOCALE
\w, \W, \b, \B をカレント・ロケールに依存して扱います。ロケールは、扱う自然言語の違いを考慮に入れるのを手助けする目的で、C 言語ライブラリに含まれる機能です。たとえば、フランス語を処理するときには、単語にマッチさせるのに \w+ と書けることを望むでしょう。しかし、\w は文字クラス [A-Za-z] のみとマッチし、"é" や "ç" とはマッチしません。システムが適切に設定されていて、フランス語が選択されていれば、特定の C 言語の関数が、プログラムに対して "é" も文字扱いするように教えるのです。正規表現をコンパイルするときに LOCALE フラグを設定すると、コンパイル済みオブジェクトが \w を扱うときに、こういう C 言語の関数を使うことになります。この場合処理は遅くなりますが、期待どおり \w+ がフランス語の単語にマッチします。

 

M, MULTILINE
通常、^ は行頭にのみ、$ は(もし存在すれば)newline 直前の行末にのみマッチします。このフラグを指定すると、^ は文字列の先頭と、文字列中で newline の直後に続く行頭にマッチします。同様に、メタ文字 $ は、文字列の最後と(newline 直前の)行末にマッチします。
S, DOTALL
特殊文字 "." が改行文字も含めて、あらゆる文字にマッチするようにします。このフラグを指定しないと、"." は改行文字以外のあらゆる文字にマッチします。
X, VERBOSE
このフラグを指定すると、柔軟な書式で、正規表現を読みやすく書くことができます。文字クラスやエスケープされていないバックスラッシュで生成された空白文字を除いて、正規表現中の空白文字は無視されます。これにより、正規表現を分かりやすくまとめたり、インデントしたりできるのです。また、正規表現中にコメントを入れることもできます。文字クラスや、エスケープされていないバックスラッシュによって生成されている場合を除き、"#" 以降がコメントになります。コメントは単に無視されます。

re.VERBOSE を使った正規表現の例を以下に示します。とても読みやすいと思いませんか?

charref = re.compile(r"""
  &#                            # 数値型の開始
  (?P<char>
        [0-9]+[^0-9]                # 10進法
        | 0[0-7]+[^0-7]             # 8進法
        | x[0-9a-fA-F]+[^0-9a-fA-F] # 16進法
)
""", re.VERBOSE)

冗長表現を指定しないと、この正規表現は次のようになります。

charref = re.compile("&#(?P<char>[0-9]+[^0-9]"
"|0[0-7]+[^0-7]"
"|x[0-9a-fA-F]+[^0-9a-fA-F])")

上の例では、Python が文字列リテラルを自動的に連結するのを利用して、正規表現を小さな部分に分割していますが、それでも re.VERBOSE を使った場合に比べて、分かりにくくなります。

http://www.builderau.com.au/program/python/soa/Build-a-basic-Web-scraper-in-Python/0,2000064084,339281476,00.htm

The Web holds a truly awe inspiring amount of information, which we're all usually happy enough to access through our Web browser. There are times, however, where your programs need to access it, and you don't want to worry about the details of the HTML mark-up.

There are thousands of HTML (or SGML, or XML) parsing libraries for hundreds of languages out there, but for this example we use a Python library called BeautifulSoup which takes care of almost all of the work for you. The BeautifulSoup library is an extremely helpful tool to have at your disposal, since it not only gives you functions to search and modify your parse tree, but it also handles the broken and malformed HTML you're likely to encounter on an average Web page.

You can download the library at its Web page. It also resides in some popular software repositories, such as the apt-get repository used in the Debian and Ubuntu distributions.

We'll write a Web scraper that prints all the displayed text contained within <p> tags. This is a very simple implementation that is easy to trip up, but it should be enough to demonstrate how using the library works.

First up, we need to retrieve the source of the page that we want to scrape. The following code will take an address given on the command line and put the contents into the variable html:

 

import urllib2,sys

address = sys.argv[1]

html = urllib2.urlopen(address).read()

 

Then we need to build a parse tree using BeautifulSoup:

 

from BeautifulSoup import BeautifulSoup

soup = BeautifulSoup(html)

 

At this point the code has already been cleaned up and converted to unicode by the BeautifulSoup library, you can print soup.prettify() to get a clean dump of the source code.

Instead, what we want is to print all of the text, without the tags, so we need to find out which parts of the parse tree are text. In BeautifulSoup there are two kinds of nodes in the parse tree, plain text is represented by the NavigableString class, whereas Tags hold mark-up. Tags are recursive structures, they can hold many children, each being either other Tags or NavigableStrings.

We want to write a recursive function that takes part of the tree: if it is a NavigableString print it out, otherwise, runs the function again on each subtree. Because we can iterate over a tag's children simply by referring to that tag this is easy.

 

from BeautifulSoup import NavigableString

def printText(tags):
	for tag in tags:
		if tag.__class__ == NavigableString:
			print tag,
		else:
			printText(tag)

 

Then we just need to run that function on all the <p> tags. We can use BeautifulSoup's in built parse tree searching functions to retrieve all of them:

 

printText(soup.findAll("p"))

 

That's it. You've got a fully functioning, if basic, HTML scraper. For more help with searching the parse tree, look up the BeautifulSoup documentation.

The full code for this example is as follows:

Listing A

 

from BeautifulSoup import BeautifulSoup,NavigableString
import urllib2,sys

address = sys.argv[1]

html = urllib2.urlopen(address).read()

soup = BeautifulSoup(html)

def printText(tags):
        for tag in tags:
                if tag.__class__ == NavigableString:
                        print tag,
                else:
                        printText(tag)
        print ""

printText(soup.findAll("p"))

print "".join(soup.findAll("p", text=re.compile(".")))

http://www.python.org/dev/peps/pep-0305/
 
link< python目次 >

File API
PEP: 305
Title: CSV File API
Version: 68056
Last-Modified: 2008-12-30 04:48:55 +0100 (Tue, 30 Dec 2008)
Author: Kevin Altis <altis at semi-retired.com>, Dave Cole <djc at object-craft.com.au>, Andrew McNamara <andrewm at object-craft.com.au>, Skip Montanaro <skip at pobox.com>, Cliff Wells <LogiplexSoftware at earthlink.net>
Discussions-To: <csv at mail.mojam.com>
Status: Final
Type: Standards Track
Content-Type: text/x-rst
Created: 26-Jan-2003
Post-History: 31-Jan-2003, 13-Feb-2003


Abstract

The Comma Separated Values (CSV) file format is the most common import and export format for spreadsheets and databases. Although many CSV files are simple to parse, the format is not formally defined by a stable specification and is subtle enough that parsing lines of a CSV file with something like line.split(",") is eventually bound to fail. This PEP defines an API for reading and writing CSV files. It is accompanied by a corresponding module which implements the API.

To Do (Notes for the Interested and Ambitious)

Application Domain

This PEP is about doing one thing well: parsing tabular data which may use a variety of field separators, quoting characters, quote escape mechanisms and line endings. The authors intend the proposed module to solve this one parsing problem efficiently. The authors do not intend to address any of these related topics:

  • data interpretation (is a field containing the string "10" supposed to be a string, a float or an int? is it a number in base 10, base 16 or base 2? is a number in quotes a number or a string?)
  • locale-specific data representation (should the number 1.23 be written as "1.23" or "1,23" or "1 23"?) -- this may eventually be addressed.
  • fixed width tabular data - can already be parsed reliably.

Rationale

Often, CSV files are formatted simply enough that you can get by reading them line-by-line and splitting on the commas which delimit the fields. This is especially true if all the data being read is numeric. This approach may work for awhile, then come back to bite you in the butt when somebody puts something unexpected in the data like a comma. As you dig into the problem you may eventually come to the conclusion that you can solve the problem using regular expressions. This will work for awhile, then break mysteriously one day. The problem grows, so you dig deeper and eventually realize that you need a purpose-built parser for the format.

CSV formats are not well-defined and different implementations have a number of subtle corner cases. It has been suggested that the "V" in the acronym stands for "Vague" instead of "Values". Different delimiters and quoting characters are just the start. Some programs generate whitespace after each delimiter which is not part of the following field. Others quote embedded quoting characters by doubling them, others by prefixing them with an escape character. The list of weird ways to do things can seem endless.

All this variability means it is difficult for programmers to reliably parse CSV files from many sources or generate CSV files designed to be fed to specific external programs without a thorough understanding of those sources and programs. This PEP and the software which accompany it attempt to make the process less fragile.

Existing Modules

This problem has been tackled before. At least three modules currently available in the Python community enable programmers to read and write CSV files:

  • Object Craft's CSV module [2]
  • Cliff Wells' Python-DSV module [3]
  • Laurence Tratt's ASV module [4]

Each has a different API, making it somewhat difficult for programmers to switch between them. More of a problem may be that they interpret some of the CSV corner cases differently, so even after surmounting the differences between the different module APIs, the programmer has to also deal with semantic differences between the packages.

Module Interface

This PEP supports three basic APIs, one to read and parse CSV files, one to write them, and one to identify different CSV dialects to the readers and writers.

Reading CSV Files

CSV readers are created with the reader factory function:

obj = reader(iterable [, dialect='excel']
             [optional keyword args])

A reader object is an iterator which takes an iterable object returning lines as the sole required parameter. If it supports a binary mode (file objects do), the iterable argument to the reader function must have been opened in binary mode. This gives the reader object full control over the interpretation of the file's contents. The optional dialect parameter is discussed below. The reader function also accepts several optional keyword arguments which define specific format settings for the parser (see the section "Formatting Parameters"). Readers are typically used as follows:

csvreader = csv.reader(file("some.csv"))
for row in csvreader:
    process(row)

Each row returned by a reader object is a list of strings or Unicode objects.

When both a dialect parameter and individual formatting parameters are passed to the constructor, first the dialect is queried for formatting parameters, then individual formatting parameters are examined.

Writing CSV Files

Creating writers is similar:

obj = writer(fileobj [, dialect='excel'],
             [optional keyword args])

A writer object is a wrapper around a file-like object opened for writing in binary mode (if such a distinction is made). It accepts the same optional keyword parameters as the reader constructor.

Writers are typically used as follows:

csvwriter = csv.writer(file("some.csv", "w"))
for row in someiterable:
    csvwriter.writerow(row)

To generate a set of field names as the first row of the CSV file, the programmer must explicitly write it, e.g.:

csvwriter = csv.writer(file("some.csv", "w"), fieldnames=names)
csvwriter.write(names)
for row in someiterable:
    csvwriter.write(row)

or arrange for it to be the first row in the iterable being written.

Managing Different Dialects

Because CSV is a somewhat ill-defined format, there are plenty of ways one CSV file can differ from another, yet contain exactly the same data. Many tools which can import or export tabular data allow the user to indicate the field delimiter, quote character, line terminator, and other characteristics of the file. These can be fairly easily determined, but are still mildly annoying to figure out, and make for fairly long function calls when specified individually.

To try and minimize the difficulty of figuring out and specifying a bunch of formatting parameters, reader and writer objects support a dialect argument which is just a convenient handle on a group of these lower level parameters. When a dialect is given as a string it identifies one of the dialects known to the module via its registration functions, otherwise it must be an instance of the Dialect class as described below.

Dialects will generally be named after applications or organizations which define specific sets of format constraints. Two dialects are defined in the module as of this writing, "excel", which describes the default format constraints for CSV file export by Excel 97 and Excel 2000, and "excel-tab", which is the same as "excel" but specifies an ASCII TAB character as the field delimiter.

Dialects are implemented as attribute only classes to enable users to construct variant dialects by subclassing. The "excel" dialect is a subclass of Dialect and is defined as follows:

class Dialect:
    # placeholders
    delimiter = None
    quotechar = None
    escapechar = None
    doublequote = None
    skipinitialspace = None
    lineterminator = None
    quoting = None

class excel(Dialect):
    delimiter = ','
    quotechar = '"'
    doublequote = True
    skipinitialspace = False
    lineterminator = '\r\n'
    quoting = QUOTE_MINIMAL

The "excel-tab" dialect is defined as:

class exceltsv(excel):
    delimiter = '\t'

(For a description of the individual formatting parameters see the section "Formatting Parameters".)

To enable string references to specific dialects, the module defines several functions:

dialect = get_dialect(name)
names = list_dialects()
register_dialect(name, dialect)
unregister_dialect(name)

get_dialect() returns the dialect instance associated with the given name. list_dialects() returns a list of all registered dialect names. register_dialects() associates a string name with a dialect class. unregister_dialect() deletes a name/dialect association.

Formatting Parameters

In addition to the dialect argument, both the reader and writer constructors take several specific formatting parameters, specified as keyword parameters. The formatting parameters understood are:

  • quotechar specifies a one-character string to use as the quoting character. It defaults to '"'. Setting this to None has the same effect as setting quoting to csv.QUOTE_NONE.
  • delimiter specifies a one-character string to use as the field separator. It defaults to ','.
  • escapechar specifies a one-character string used to escape the delimiter when quotechar is set to None.
  • skipinitialspace specifies how to interpret whitespace which immediately follows a delimiter. It defaults to False, which means that whitespace immediately following a delimiter is part of the following field.
  • lineterminator specifies the character sequence which should terminate rows.
  • quoting controls when quotes should be generated by the writer. It can take on any of the following module constants:
    • csv.QUOTE_MINIMAL means only when required, for example, when a field contains either the quotechar or the delimiter
    • csv.QUOTE_ALL means that quotes are always placed around fields.
    • csv.QUOTE_NONNUMERIC means that quotes are always placed around nonnumeric fields.
    • csv.QUOTE_NONE means that quotes are never placed around fields.
  • doublequote controls the handling of quotes inside fields. When True two consecutive quotes are interpreted as one during read, and when writing, each quote is written as two quotes.

When processing a dialect setting and one or more of the other optional parameters, the dialect parameter is processed before the individual formatting parameters. This makes it easy to choose a dialect, then override one or more of the settings without defining a new dialect class. For example, if a CSV file was generated by Excel 2000 using single quotes as the quote character and a colon as the delimiter, you could create a reader like:

csvreader = csv.reader(file("some.csv"), dialect="excel",
                       quotechar="'", delimiter=':')

Other details of how Excel generates CSV files would be handled automatically because of the reference to the "excel" dialect.

Reader Objects

Reader objects are iterables whose next() method returns a sequence of strings, one string per field in the row.

Writer Objects

Writer objects have two methods, writerow() and writerows(). The former accepts an iterable (typically a list) of fields which are to be written to the output. The latter accepts a list of iterables and calls writerow() for each.

Implementation

There is a sample implementation available. [1] The goal is for it to efficiently implement the API described in the PEP. It is heavily based on the Object Craft csv module. [2]

Testing

The sample implementation [1] includes a set of test cases.

Issues

  1. Should a parameter control how consecutive delimiters are interpreted? Our thought is "no". Consecutive delimiters should always denote an empty field.

  2. What about Unicode? Is it sufficient to pass a file object gotten from codecs.open()? For example:

    csvreader = csv.reader(codecs.open("some.csv", "r", "cp1252"))
    
    csvwriter = csv.writer(codecs.open("some.csv", "w", "utf-8"))
    

    In the first example, text would be assumed to be encoded as cp1252. Should the system be aggressive in converting to Unicode or should Unicode strings only be returned if necessary?

    In the second example, the file will take care of automatically encoding Unicode strings as utf-8 before writing to disk.

    Note: As of this writing, the csv module doesn't handle Unicode data.

  3. What about alternate escape conventions? If the dialect in use includes an escapechar parameter which is not None and the quoting parameter is set to QUOTE_NONE, delimiters appearing within fields will be prefixed by the escape character when writing and are expected to be prefixed by the escape character when reading.

  4. Should there be a "fully quoted" mode for writing? What about "fully quoted except for numeric values"? Both are implemented (QUOTE_ALL and QUOTE_NONNUMERIC, respectively).

  5. What about end-of-line? If I generate a CSV file on a Unix system, will Excel properly recognize the LF-only line terminators? Files must be opened for reading or writing as appropriate using binary mode. Specify the lineterminator sequence as 'rn'. The resulting file will be written correctly.

  6. What about an option to generate dicts from the reader and accept dicts by the writer? See the DictReader and DictWriter classes in csv.py.

  7. Are quote character and delimiters limited to single characters? For the time being, yes.

  8. How should rows of different lengths be handled? Interpretation of the data is the application's job. There is no such thing as a "short row" or a "long row" at this level.

References

[1] (1, 2) csv module, Python Sandbox (http://cvs.sourceforge.net/cgi-bin/viewcvs.cgi/python/python/nondist/sandbox/csv/)
[2] (1, 2) csv module, Object Craft (http://www.object-craft.com.au/projects/csv)
[3] Python-DSV module, Wells (http://sourceforge.net/projects/python-dsv/)
[4] ASV module, Tratt (http://tratt.net/laurie/python/asv/)

There are many references to other CSV-related projects on the Web. A few are included here.



忍者ブログ [PR]
お天気情報
カレンダー
03 2024/04 05
S M T W T F S
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
リンク
フリーエリア
最新CM
最新TB
プロフィール
HN:
No Name Ninja
性別:
非公開
バーコード
ブログ内検索
P R
カウンター
ブログの評価 ブログレーダー