YutaKaのPython教室

Python の文法やライブラリ、API、環境構築について画像・動画・ソースコード付きで徹底解説!

Python テキストファイル読み込み・テキスト処理まとめ

Pythonでは、始めからインストールされている標準モジュールを使うだけで、簡単にテキストファイルを読み込むことができます。

しかし、実際にテキストファイルを読み込もうとすると、次のような問題に直面することも…

  • ファイルの読み込みってどうやるんだっけ?
  • エンコーディングのエラーがでた!
  • テキストの出力方法にも種類があって、使い分けがよくわからない!
  • 読み込み後の処理でいつも戸惑う…

そこで、この記事ではPythonでのテキストファイル読み込み、よく使うテキスト処理について、図解付きで徹底解説しています!

テキスト読み込みの基本の流れ

Pythonでのテキスト読み込みの基本の流れは次の通りです。

  1. open()関数などでテキストファイルを開く
  2. .read()メソッド等でファイルの中身を読み込む

ファイルの開き方や読み込み方法で、細かな設定は必要ですが、大きな流れは基本的に上記の通りです。

以下では、テキストファイルの開き方、読み込み方について、順番に詳しく解説していきます。

ファイルを開く

ファイルを開くには、Pythonの組み込み関数open()を使用します。

引数で開きたいファイルのパスを指定すればOKです。

  • open(ファイルのパス)
    • Ex1:作業フォルダ内のファイルを相対パスで指定 ⇒ open(ファイル名)
    • Ex2:絶対パスでファイルを指定 ⇒ open("C:\Users\YutaKa\Documents\ファイル名")

open()でファイルを開く場合、次の2パターンがあります。

  1. open()関数で開いて、.close()で閉じる
  2. with open()の構文でファイルを開く(ファイルは自動で閉じられる)

open()関数でファイルを開く場合には、閉じ忘れに注意する必要がありますが、with open構文であれば閉じ忘れの心配はありません。

open()関数で開く例、with open()構文で開く例の順番で実行例を見てみましょう。

open()関数で開く

まずは、open()関数でファイルを開く例を見てみます。

ここでは、作業フォルダ内にsample.txtがあるものとして、一連の処理を記述してみます。

f = open("sample.txt")

# テキスト読み込みなどの処理文

f.close()

この例は、次のような処理フローになっています:

  1. open()関数で開いたファイルを変数fに格納
  2. 何かしらの処理をする
  3. f.close()でファイルを閉じる

open()関数開いたファイルは自動では閉じられません。

最後にf.close()でファイルを閉じるのを忘れないように気を付けましょう。

with open()構文で開く

次にwith open()構文でファイルを開く方法を紹介します。

with open()構文でファイルを開くと、withブロックが終わった後に自動でファイルを閉じてくれます。

ファイルの閉じ忘れの心配がなくなるので、with open()構文はぜひ覚えてほしいテクニックです。

with open("sample.txt") as f:
    # 処理文

この例は、次のような処理フローになっています。

  1. with open()構文で、ファイルを変数fとして開く
  2. 何かしらの処理をする
  3. with構文から抜けると自動で、ファイルが閉じられる

with open構文はファイルの閉じ忘れがなくて便利です。

一方、ファイルを閉じてしまうので、プログラムの別の場所で再度ファイルを利用することはできません。

状況に応じて、open()関数とwith open()構文を使い分けましょう。

エンコーディングの指定

ファイル読み込みの際によくあるトラブルは、エンコードに関するものです。

誤解を恐れずに超ざっくりエンコードを説明すると、次のようなイメージです。

  • そもそもPC上のデータは、0と1で記録されている
  • 0と1のデータを文字列に変換するときに特定のルールに従う必要がある
  • このルールの種類がエンコードと呼ばれるもの

図にすると次のようなイメージです。

Pythonのopen()関数では、OSに応じたエンコーディングが自動で選ばれます(個人個人の環境によって上の表以外になる場合もあります。)。

OS エンコーディング
Windows cp932
macOX UTF-8
Linux UTF-8

例えば、開こうとしているファイルがUTF-8のルールで記述されているのに、cp932のルールで開こうとすると次のような問題が起こります。

  • ファイルは開けるが、違うルールで変換しているので文字化けする
  • cp932のルールでは解読できない文字があって、エラーを出す

Windowsのメモ帳であれば、ウィンドウ右下でエンコードが確認できます。

UTF-8でエンコードされているテキストファイルをWindowsのデフォルト設定(cp932)で開いた際のエラー例を見てみましょう。

with open("sample.txt") as f:
    text = f.read()
# UnicodeDecodeError: 'cp932' codec can't decode byte 0xef in position 25: illegal multibyte sequence

cp932では、読めない文字があるとエラーが出てしまいました。

こういった場合は、open()関数の引数encondingで正しいエンコードを指定します。

  • encoding="エンコード名"

今回の場合は、encoding="UTF-8"を指定します。

with open("sample.txt", encoding="UTF-8") as f:
    text = f.read()
# エラーが出ずにファイルを読み込めた

テキストを読み込む

open()でファイルを開いた後は、.read()メソッドなどでファイルを読み込みます。

基本的な読み取り方法は次の通りです。

読み込み方法 メソッド コメント
ファイル全体(文字列) .read() ファイルの内容全体を文字列で一括取得
ファイル全体(行ごとのリスト) .readlines() [一行目, 二行目,…]形式のリスト
指定した文字数のみ .read(文字数) 数文字ずつ取得したい場合に便利
一行ずつ読み込み .readline() or
for文で読み込み
一行ずつ読み取りたい場合に便利

例えばファイルを変数fで開いている場合は、次のようにメソッドを呼び出して文字列を読み込みます。

  • text = f.read()

この例では、ファイルの内容全体が文字列として変数textに格納されます。

次のサンプルファイルを使って、各読み込み方法の実行例を確認してみましょう。

ファイル全体読み込み(文字列)

ファイル全体を文字列で一括取得したい場合は、.read()メソッドを使用します。

  • text = f.read()

サンプルファイルで実行例を確認してみましょう。

with open("sample.txt", encoding="UTF-8") as f:
    text = f.read()
    
text
# '一行目です。\n二行目です。\n三行目です。'

無事、ファイルの中身が取得できましたね。

改行コード\nに慣れていない方のために補足です。

変数textに格納されているときは、改行は改行コード\nで記述されています。

一方、print()関数で出力すると改行コードは\nに展開(変換)されます。

print(text)
# 一行目です。
# 二行目です。
# 三行目です。

print()関数で改行コードが展開されることを覚えておくと、\nで混乱することもなくなると思います。

ファイル全体読み込み(リスト)

ファイル全体を行ごとのリストで一括取得したい場合は、.readlines()メソッドを使用します。

  • text_list = f.readlines()
    • 結果 ⇒ ["一行目", "二行目", "三行目", …]

サンプルファイルで実行例を確認してみましょう。

with open("sample.txt", encoding="UTF-8") as f:
    text_list = f.readlines()
    
text_list
# ['一行目です。\n', '二行目です。\n', '三行目です。']

サンプルファイルは、3行のなので要素数が3のリストができあがりましたね。

  • 【参考】文末の改行文字の削除方法については、次の記事で解説しています。
≫【コピペOK!】改行を削除するPythonスクリプト
pyhonでテキストファイルを読み込んだ時に、改行コード\nが残って困ってしまうことがありますね。今回はテキストデータから改行コードを削除する方法を3つ紹介します。①文字列の置換メソッド:replace()で削除、②reモジュールの置換メソッド:re.subで削除、③文字列の末尾削除メソッド:rstrip()で削除。応用例として、クリップボードにコピーしたテキストから、改行コードを削除する効率化技も紹介しています。
www.yutaka-note.com/entry/linebreak
 
指定した文字数のみ読み込み

指定した文字数ずつ読み込みたい場合は、.readl()メソッドに文字数を渡します。

  • text = f.read(指定文字数)

ファイルを開いたまま、.read(指定文字数)を繰り返すと、読み込み開始位置が指定文字数ずつずれていきます。

そのため、.read(指定文字数)を繰り返すことで、指定文字数ずつ読み込めます。

サンプルファイルで実行例を確認してみましょう。

with open("sample.txt", encoding="UTF-8") as f:
    text1 = f.read(1)
    text2 = f.read(2)
    text3 = f.read(3)
    
    print(text1)
    print(text2)
    print(text3)

上記の例では、3文字ずつ読み取られていますね。

また、ファイルの最後に到達済みの場合は、""(空文字)を返します。

for文で繰り返しテキストを読み込み、ファイルの最後に到達する例を見てみます。

my_list = []

with open("sample.txt", encoding="UTF-8") as f:
    for i in range(7):
        text = f.read(5)
        my_list.append(text)
my_list
# ['一行目です',
#  '。\n二行目',
#  'です。\n三',
#  '行目です。',
#  '',
#  '',
#  '']

最後の3回は文末に到達して空文字が出力されていますね。

for文やwhile文で回したい場合は、""(空文字)で条件を設定すると良いです。

while文で""を利用してループを抜ける例を見てみます。

my_list = []

with open("sample.txt", encoding="UTF-8") as f:
    while text := f.read(10): # :=は代入演算子(代入してからwhileで評価される)
        my_list.append(text)
my_list
# ['一行目です。\n二行目', 'です。\n三行目です。']

while文の条件判定で、""Falseとして扱われることを利用してループを抜けています。

一行ずつ読み込む

ファイルを一行ずつ読み込む場合は、.readline()メソッドを使用します。

  • line = f.readline()

ファイルを開いたまま、.readline()を繰り返すと一行ずつ読み込みが行われます。

.read(指定文字数)と同じように、ファイルの末尾に到達すると""(空文字)を返します。

サンプルファイルで実行例を確認してみましょう。

my_list = []

with open("sample.txt", encoding="UTF-8") as f:
    for i in range(10):
        line = f.readline()
        my_list.append(line)
        
my_list
# ['一行目です。\n', '二行目です。\n', '三行目です。', '', '', '', '', '', '', '']

文末に到達すると空文字が出力されていますね。

while文で""を利用してループを抜ける例を見てみます。

my_list = []

with open("sample.txt", encoding="UTF-8") as f:
    while line := f.readline():
        my_list.append(line)
        
my_list
# ['一行目です。\n', '二行目です。\n', '三行目です。']
for分で一行ずつ読み込む

open()で開いたファイルを一行ずつ読み込む場合は、for文を使用してシンプルに実行することも可能です。

次の形式で記述すると、ファイルの冒頭から末尾まで一行ずつ読み取ることができます。

my_list = []
with open(ファイル名) as f:
    for line in f:
        # 処理文

この記述方法では、次のような挙動になります。

  • forループでファイルの中身が一行ずつ変数lineに読み込まれる
  • 文末に到達するとforループを抜ける

for文を抜ける条件を書く必要がないという点では、.readline()よりも便利ですね。

サンプルファイルで実行例を確認してみましょう。

with open("sample.txt", encoding="UTF-8") as f:
    for line in f:
        print(line, end="")
# 一行目です。
# 二行目です。
# 三行目です。

シンプルな記述で、ファイルの一行目から三行目まで出力されましたね。

これでテキストファイルの読み込みについての処理については網羅できたと思います。

よく使うテキスト処理まとめ

以下では、実務上よく使うテキスト処理について、汎用性の高いものをまとめます。

  1. print()関数の改行調整
  2. テキストをスペース、カンマで分割
  3. テキストの結合
  4. 特定の文字で始まる/終わるか判定
  5. 特定の文字を含むか判定
  6. 正規表現で特定のパターンを抽出

実際には、これらに加えて複雑な処理を追加することが多いですが、ここでは基本的なものを整理しています。

print()関数の改行調整

テキストファイルをprint()関数で出力する際の改行に関する注意点について解説します。

f.readline()などで読み込んだテキストをprint()関数で出力すると、行と行の間に不要な空欄行が出力されることがあります。

with open("sample.txt", encoding="UTF-8") as f:
    for line in f:
        print(line)
# 一行目です。
                        # ← 不要な空欄行
# 二行目です。
                        # ← 不要な空欄行
# 三行目です。

これは、各行で次の理由で各行に二重で改行が適用されてしまうためです。

  1. print()関数は、実行ごとに自動で改行(改行1個目)
  2. 読み取りデータ行末の\n改行コードで改行(改行2個目)

不要な空欄行を避けるためには、print()関数実行時の改行を省く設定にするのがおすすめです。

次のようにend引数に空文字を適用しましょう。

  • print(出力内容, end="")

これで不要な空欄行を避けることができます。

with open("sample.txt", encoding="UTF-8") as f:
    for line in f:
        print(line, end="")
# 一行目です。
# 二行目です。
# 三行目です。

テキストの結合

読み込んだ文字列は、+で結合できます。

s1 = "あいう"
s2 = "えお"

s1+s2
# "あいうえお"

文字列のリストを結合する場合は、.join()メソッドを使用します。

  • "結合文字".join([文字列のリスト])

サンプルコードで挙動を確認してみましょう。

s = ["さ", "し", "す", "せ", "そ"]

# "_"で結合
"_".join(s)
'さ_し_す_せ_そ'

# " "で結合
" ".join(s)
'さ し す せ そ'

# "-"で結合
"-".join(s)
'さ-し-す-せ-そ'
  • 【参考】文字列の結合については、次の記事で詳しく解説しています。
≫Python 文字列の分割方法まとめ|図解付で分かりやすく解説
文字列の分割は、文字列の解析やデータ処理などで頻繁に行われる作業の一つですね。Pythonには、文字列を簡単に分割するための機能が豊富に用意されています。しかし、実際に文字列の分割を行おうとすると、次のような課題に直面することも…。① 特定の文字でテキストを分割する方法は?② テキストを行ごとに分割する方法は?③ 正規表現を使って複雑な分割処理を行いたい! この記事では、Pythonの文字列分割について図解付きでわかりやすく解説していきます。
www.yutaka-note.com/entry/bf_split
 

テキストをスペース、カンマで分割

読み込んだテキストを、スペースやカンマで分割する処理をすることもよくあります。

テキストの分割には、.split()メソッドを使用します。

  • 空白文字で分割:"文字列".split()
  • 分割文字を指定:"文字列".split("分割文字")
    • Ex) カンマで分割:"文字列".split(",")

次のサンプルファイルで実行例を確認してみます。

各行を読み込んで、料理名と何人前で分割してみます。

with open("sample2.txt", encoding="UTF-8") as f:
    for line in f:
        my_list = line.split()
        print(my_list)

# ['お寿司', '10人前', '10000円']
# ['焼き肉', '5人前', '5000円']
# ['肉野菜炒め', '10人前', '2000円']
# ['おにぎり', '10人前', '1000円']

このパターンを使えば、テキストを読み取りながら様々な文字で分割することが可能です。

  • 【参考】テキスト分割については、次の記事で詳しく解説しています。
≫Python 文字列の分割方法まとめ|図解付で分かりやすく解説
文字列の分割は、文字列の解析やデータ処理などで頻繁に行われる作業の一つですね。Pythonには、文字列を簡単に分割するための機能が豊富に用意されています。しかし、実際に文字列の分割を行おうとすると、次のような課題に直面することも…。① 特定の文字でテキストを分割する方法は?② テキストを行ごとに分割する方法は?③ 正規表現を使って複雑な分割処理を行いたい! この記事では、Pythonの文字列分割について図解付きでわかりやすく解説していきます。
www.yutaka-note.com/entry/bf_split
 

特定の文字で始まるか判定

特定の文字列で始まるか判定する場合には、.startswith()メソッドを使用します。

  • "文字列".startswith("対象文字")

"文字列""対象文字"で始まる場合はTrueを、そうでない場合はFalseを返します。

サンプルファイルで、"お"から始まる行だけ出力してみましょう。

with open("sample2.txt", encoding="UTF-8") as f:
    for line in f:
        if line.startswith("お"):
            print(line, end="")
# お寿司 10人前 10000円
# おにぎり 10人前 1000円

特定の文字を含むか判定

特定の文字列を含むか判定する場合には、in演算子を使用します。

  • "対象文字" in "文字列"

"対象文字""文字列"に含まれる場合はTrueを、そうでない場合はFalseを返します。

サンプルファイルで、"肉"を含む行を出力してみましょう。

with open("sample2.txt", encoding="UTF-8") as f:
    for line in f:
        if "肉" in line:
            print(line, end="")

# 焼き肉 5人前 5000円
# 肉野菜炒め 10人前 2000円

正規表現で特定のパターンを抽出

読み込み文字列から複雑な抽出をしたい場合は、正規表現を使用しましょう。

正規表現で「食事名 個数 金額」のパターンを作って、サンプルファイルからデータを抽出、辞書型に成形してみます。

import re
p = re.compile("(\w+)\s(\d+)\w+\s(\d+)\w+")

my_dict = {}
with open("sample2.txt", encoding="UTF-8") as f:
    for line in f:
        m = p.search(line)
        food = m.group(1)
        num = int(m.group(2))
        price = int(m.group(3))
        my_dict[food] = {"Num": num, "Price": price}
   
my_dict
# {'お寿司': {'Num': 10, 'Price': 10000}, 
#  '焼き肉': {'Num': 5, 'Price': 5000}, 
#  '肉野菜炒め': {'Num': 10, 'Price': 2000},
#  'おにぎり': {'Num': 10, 'Price': 1000}}

正規表現と組み合わせると簡単にデータの抽出を行うことができますね。

正規表現に慣れていない方は、次の記事でわかりやすく解説しているので参考にしてください。

≫Python正規表現パターンを図解&サンプルで本当にわかりやすく解説
Pythonのreモジュールで正規表現パターンを作成する際に重要な3つのポイントを解説します。①特定の文字の繰り返しを指定 ②数値や文字、空白など条件による文字条件の指定 ③グループ化によるパターン内の特定部分の抽出
www.yutaka-note.com/entry/regex_01
 
≫Python 正規表現の検索を本当にわかりやすく解説
Pythonで正規表現を使用して、パターン検索する方法をわかりやすく解説しています。① 検索メソッド(関数)の実行方法② 各メソッドの紹介(単独検索search()、全体検索findallなど)③ 検索結果の出力であるMatchオブジェクトの内容
www.yutaka-note.com/entry/regex_02
 

また、テキストファイルを辞書型に成形できれば、その後は簡単にpandasDataFrameに変換、データ分析につなげられます。

import pandas as pd
df = pd.DataFrame.from_dict(my_dict).T
df

辞書型からのDataFrameの生成方法がわからない場合は、次の記事でわかりやすく解説しているので参考にしてください。

≫pandas 辞書型からDataFrameを生成|インデックスとコラムの設定も!
pandasでは、辞書型からデータフレームを生成することもできます。しかし、pandasの操作に慣れていないうちは、ちょっとした操作も難しいですよね。この記事では、①辞書型からデータフレームを生成する方法、②辞書のキーをインデックス名、コラム名それぞれに設定する方法、③DataFrame()とDataFrame.from_dict()の違いをサンプルコード付きで紹介!
www.yutaka-note.com/entry/pandas_dict
 

オススメ|データ分析の勉強方法

今回はPythonでテキストファイルを読み込む方法について解説しました。

データ分析初心者の方にはこちらの記事もおススメです。

≫独学でデータ分析を勉強するオススメ学習本
独学でのpythonデータ分析勉強に役立ったおススメ書籍を紹介していきます。業務でそれなりにデータ分析を行えるまで、いろいろな試行錯誤をしてきましたが、もし自分が今ゼロから勉強する立場ならどうするのがいいのか考えてみました。以下では、入門書、個別モジュール用、実践用の3つの視点でおススメ本を紹介していきます。
www.yutaka-note.com/entry/data_analysis
 

Pythonで作業の効率化を目指している方には、次の記事がおススメです。

≫【レビュー】退屈なことはPythonにやらせよう | 単純作業は自動化
名著「退屈なことはPythonにやらせよう 」をレビュー!単純作業を自動化して、自分が本当にしたいことのために時間を確保していこう!所要時間やできるようになったことを紹介!この本を参考にして作成した、自作自動化スクリプトも紹介しています!
www.yutaka-note.com/entry/python_jido
 
≫【レビュー】Python自動処理 全部入り。 | 即効性重視!Pythonによる自動化事例集!
今回は、Pythonの作業自動処理の解説本『Python自動処理全部入り』をレビュー!この本は、特定の作業に特化した即効性の重視の自動化解説本です。よくある作業から自動化しやすい作業を抽出して、それらの自動化を集中的に解説した本という印象です。本の中に自分のニーズに合った自動化処理が見つかれば、その日からすぐ使える事例集のような印象です。
www.yutaka-note.com/entry/python_jido_zenbu
 

Python 文字列の結合方法まとめ|図解付で分かりやすく解説

文字列の結合は、文字列の解析やデータ処理などで頻繁に行われる作業の一つですね。

Pythonには、文字列を簡単に結合するための機能が豊富に用意されています。

しかし、実際に文字列を結合しようとすると、次のような課題に直面することも…。

  • 複数の文字列を簡単に結合する方法は?
  • リスト化された文字列を一括で結合する方法はある?
  • テキストに文字列を埋め込みたい!

この記事では、Pythonの文字列結合について図解付きでわかりやすく解説していきます。

文字列結合方法まとめ

Pythonにおける文字列の結合には主に次のような種類があります。

  1. シンプルに複数の文字列を連結する
  2. [リスト]内の文字列を結合する
  3. 既存テキスト内に文字列を埋め込む

これらの結合方法を以下の表にまとめているので、一覧を確認してみましょう。

分類 結合方法 実行例, 適用
"文字列"の連結 連結
"文字列1"+"文字列2"
"あいう"+"えお""あいうえお"
  連結→代入
"変数1" += "変数2"
s1="かきく", s2="けこ"
s1+=s2s1="かきくけこ"
  繰り返し
"文字列" * 繰り返し回数

"あい"*3"あいあいあい"
[文字列リスト]の結合 "結合文字".join([リスト]) l=["さ", "し", "す", "せ", "そ"]
"_".join(l)"さ_し_す_せ_そ"
テキストに埋め込み f-stringで埋め込み
※Python3.6以降
name="田中", f"{name}さん"
"田中さん"
  .format()で埋め込み
※Python2.6以降
name="田中", "{}さん".format(name)
"田中さん"
  %演算子で埋め込み
※Python3.0以降は非推奨
name="田中", "%sさん" % name
"田中さん"

文字列の結合は、テキスト処理の基本操作の一つです。

例を挙げればキリがありませんが、例えば次のような処理でも使用します。

  • テキスト処理
  • ファイルパスの結合
  • ウェブスクレイピング結果の整理
  • 各種クエリの結合など

以下では、各文字列の結合方法について、サンプルコード付きで詳しく解説していきます。

しっかり結合方法を覚えて、適切に処理できるようになりましょう!

+で文字列を連結

最も基本的な文字列の結合方法は、2つ以上の文字列を+で繋ぐ方法です。

  • "文字列1"+"文字列2"

結合するのは、文字列のシリアル、文字列の変数どちらでもOKです。

# 文字列のシリアル値を結合
"あいう" + "えお"
# 'あいうえお'

# 文字列の変数を結合
s1 = "かき"
s2 = "く"
s3 = "けこ"
s1 + s2 + s3
# 'かきくけこ'

文字列の連結は非常にシンプルで簡単ですね。

この他の処理方法についても、しっかりおさえて行きましょう。

文字列に数値を連結

+で連結できるのは、文字列同士のみです。

文字列と数値を+で結合しようとすると、次のようなエラーが発生します。

name = "田中"
age = 20

name + "さんは、"+ age +"歳です。"
# TypeError: can only concatenate str (not "int") to str

文字列と数値を連結したい場合には、str()で数値を文字列に変換します。

  • "文字列" + str(数値)

サンプルコードで挙動を確認してみます。

name = "田中"
age = 20

name + "さんは、"+ str(age) +"歳です。"
# '田中さんは、20歳です。'

問題なく、文字列と数値を結合することができましたね。

ここでは、+で結合する方法を紹介しましたが、文字列と数値を結合する際は、後述するf-stringで埋め込みも便利です。

+=で文字列を連結(代入)

+で結合する際の、ちょっとしたテクニックとして、+=での結合があります。

左側の変数に右側の変数を結合して、左側の変数に代入することができます。

  • s1 += s2
    • s1s1+s2の値を代入

サンプルコードで実行例を確認してみましょう。

s1 = "あいう"
s2 = "えお"

s1+=s2
s1
"あいうえお"

文字列の繰り返し

同じ文字列を複数回繰り返す場合は、対象文字列に繰り返し回数をかけます。

  • "文字列" * 繰り返し回数
"あい" * 3
# 'あいあいあい'

s = "すももも"
s * 3 
# 'すもももすもももすももも'

テキスト処理の際には、文字列を繰り返すこともあるので、覚えておきたい内容ですね。

[文字列リスト]の結合|.join()

.join()メソッドを使用すると、[文字列のリスト]の要素を指定の文字列で結合できます。

  • "結合文字".join([文字列のリスト])

.join()を実行すると[文字列のリスト]結合文字で結合した文字列が返されます。

サンプルコードで挙動を確認してみましょう。

s = ["さ", "し", "す", "せ", "そ"]

# "_"で結合
"_".join(s)
'さ_し_す_せ_そ'

# " "で結合
" ".join(s)
'さ し す せ そ'

# "-"で結合
"-".join(s)
'さ-し-す-せ-そ'

.join()メソッドでは、[リスト]だけでなく、(タプル){セット}などのシーケンスに適用できます。

# 文字列のタプルの結合例
s = ("さ", "し", "す", "せ", "そ")
"-".join(s)
'さ-し-す-せ-そ'

# 文字列のセットの結合例
s = {"さ", "し", "す", "さ", "し"}
# {'さ', 'し', 'す'}

"-".join(s)
# 'し-す-さ'

  • 【参考】逆の操作である文字列の分割については、以下の記事で解説しています。
≫Python 文字列の分割方法まとめ|図解付で分かりやすく解説
文字列の分割は、文字列の解析やデータ処理などで頻繁に行われる作業の一つですね。Pythonには、文字列を簡単に分割するための機能が豊富に用意されています。しかし、実際に文字列の分割を行おうとすると、次のような課題に直面することも…。① 特定の文字でテキストを分割する方法は?② テキストを行ごとに分割する方法は?③ 正規表現を使って複雑な分割処理を行いたい! この記事では、Pythonの文字列分割について図解付きでわかりやすく解説していきます。
www.yutaka-note.com/entry/bf_split
 

数値のリストを結合

.join()は、[文字列のリスト等]を結合するメソッドです。

[数値のリスト]は、そのままでは結合できません。

date = [1998, 10, 31]

"-".join(date)
# TypeError: sequence item 0: expected str instance, int found

[数値のリスト]は、リスト内包表記で数値を文字列に変換してから、.join()を適用しましょう。

date = [1998, 10, 31]
date_str = [str(n) for n in date] # リスト内包表記で文字列に変換

"-".join(date_str)
# '1998-10-31'

# 一行で書く場合
date_str = [str(n) for n in date]
"-".join([str(n) for n in date])
# '1998-10-31'
  • 【参考】文字列の日付をpandasDatetimeに変換する方法は以下の記事で紹介しています。
≫pandas 文字列⇒Datetime変換|時系列操作を簡単マスター!
pandasで"文字列"をDatetimeに変換する方法、Datetimeの基本的な操作方法を解説します。"文字列"のままでは、日時関連の計算や条件判定が自由にできません…。Datetimeに変換して、日付の差分計算や便利な機能を使えるようにしましょう!
www.yutaka-note.com/entry/pandas_datetime
 

テキストに文字列を埋め込み

文字列の結合の特別なパターンとして、テキストに文字列を埋め込む方法を紹介します。

  • 例)"私は___です。"という雛形に、name="田中"を埋め込んで、"私は田中です。"というテキストを作成

Pythonでテキストに文字列を埋め込む方法は以下の通りです。

構文・メソッド 使用方法
f-string 文字列の前にfをつけて、文字列の中に{変数 or 式}を記述
※Python3.6以降で使用可能
.format() 文字列中で{}を用意し、.format()で変数や式を与える
※Python2.6以降で使用可能
%演算子 文字列中でフォーマット指定子を用意し、%演算子の右側に変数を与える
※Python3.0以降で非推奨

バージョンの制限がない限りは、基本的にはf-stringを使用することをおすすめします。

変数や式を直接記述できるうえに、可読性も高く、最も使いやすいです。

f-stringで埋め込み

f-stringは、Python3.6から導入された文字列フォーマットで、文字列中に変数や式の値を埋め込むことができます。

文字列の前にfをつけて、文字列の中に{変数 or 式}を記述します。

  • f"文字列の中に{リテラル, 変数, or式}を記述"

サンプルコードで使用方法を確認してみましょう。

name = "田中"
age = 20

f"{name}さんは、{age}歳です。"
# '田中さんは、20歳です。'

f-stringの中で式を使うことも可能です。

f"{name}さんは、{age*3+5}歳です。"
# '田中さんは、65歳です。'

.format()で埋め込み

".format()"は、文字列をフォーマットするためのメソッドです。

文字列の中にプレースフォルダー{}を用意して、.format()メソッドで値を代入します。

  • "文字列の中に{}を記述".format(リテラル, 変数, or式)

サンプルコードで使用方法を確認してみましょう。

name = "田中"
age = 20

"{}さんは、{}歳です。".format(name, age)
# '田中さんは、20歳です。'

%演算子で埋め込み

%演算子は、C言語のprintf()関数のフォーマット指定子に似た方法です。

文字列の中に、変数の型に応じたフォーマット指定子を準備して、%( )で値を代入します。

主なフォーマット指定子は次の通りです。

フォーマット指定子 説明
%s 文字列
%d 整数
%f 浮動小数点
%e 指数表記の浮動小数点

サンプルコードで使用方法を確認してみましょう。

name = "田中"
age = 20

"%sさんは、%d歳です。" %(name, age)
# '田中さんは、20歳です。'

ただし、Python3.0以降で非推奨です。

f-stringformat()というより便利な手法もあるので、よっぽどの事情がない場合は使うことはないと思います。

オススメ|データ分析の勉強方法

今回はPythonでテキストファイルを分割する方法ついて解説しました。

データ分析初心者の方にはこちらの記事もおススメです。

≫独学でデータ分析を勉強するオススメ学習本
独学でのpythonデータ分析勉強に役立ったおススメ書籍を紹介していきます。業務でそれなりにデータ分析を行えるまで、いろいろな試行錯誤をしてきましたが、もし自分が今ゼロから勉強する立場ならどうするのがいいのか考えてみました。以下では、入門書、個別モジュール用、実践用の3つの視点でおススメ本を紹介していきます。
www.yutaka-note.com/entry/data_analysis
 

Pythonで作業の効率化を目指している方には、次の記事がおススメです。

≫【レビュー】退屈なことはPythonにやらせよう | 単純作業は自動化
名著「退屈なことはPythonにやらせよう 」をレビュー!単純作業を自動化して、自分が本当にしたいことのために時間を確保していこう!所要時間やできるようになったことを紹介!この本を参考にして作成した、自作自動化スクリプトも紹介しています!
www.yutaka-note.com/entry/python_jido
 
≫【レビュー】Python自動処理 全部入り。 | 即効性重視!Pythonによる自動化事例集!
今回は、Pythonの作業自動処理の解説本『Python自動処理全部入り』をレビュー!この本は、特定の作業に特化した即効性の重視の自動化解説本です。よくある作業から自動化しやすい作業を抽出して、それらの自動化を集中的に解説した本という印象です。本の中に自分のニーズに合った自動化処理が見つかれば、その日からすぐ使える事例集のような印象です。
www.yutaka-note.com/entry/python_jido_zenbu
 

Python 文字列の分割方法まとめ|図解付で分かりやすく解説

文字列の分割は、文字列の解析やデータ処理などで頻繁に行われる作業の一つですね。

Pythonには、文字列を簡単に分割するための機能が豊富に用意されています。

しかし、実際に文字列の分割を行おうとすると、次のような課題に直面することも…。

  • 特定の文字でテキストを分割する方法は?
  • テキストを行ごとに分割する方法は?
  • 正規表現を使って複雑な分割処理を行いたい!

この記事では、Pythonの文字列分割について図解付きでわかりやすく解説していきます。

文字列分割方法まとめ

文字分割の基本の流れは次の通りです。

  1. 分割したい文字列を準備|text = "分割したい文字列"
  2. 分割用のメソッドを実行|text.分割用メソッド()

分割用のメソッドには、区切り文字で分割するものや、改行で分割するものなどがあります。

これらの方法に加えて、正規表現のreモジュールを使用した分割方法もあります。

Pythonにおける主な文字列分割方法を以下の表にまとめているので、一覧を確認してみましょう。

メソッド名・方法名 説明
split() 指定した区切り文字で文字列を分割。
→ 分割された文字列を[リスト]で返す。
※デフォルトでは空白文字が区切り文字
rsplit() split()と同様だが、文字列を右端から分割。
分割回数を指定した際にsplit()と挙動の違いがでる。
splitlines() 改行文字を区切り文字として、文字列を複数の行に分割。
→ 複数行のテキストを行ごとに分割するときに便利
partition() 指定した区切り文字を境に文字列を前後に分割。
→ 区切り文字を含めた3つの文字列を(タプル)で返す。
rpartition() partition()と同様だが、文字列を右端から分割。
正規表現で分割 reモジュールのsplit()を使うと正規表現を指定して文字列を分割できる。

split()splitlines()は、最も基本的な文字列分割メソッドです。

正規表現を使えば、柔軟に分割処理ができるので、こちらも抑えておきたい技術の一つですね。

以下では、これらの文字列分割方法について、サンプルコード付きで詳しく解説していきます。

区切り文字で分割|split()

最も基本的な文字列の分割方法です。基本挙動は次の通りです。

  • 指定した区切り文字で文字列を分割
    • デフォルト:空白文字(スペース、タブ\t、改行\n,\r\n)で分割
  • 分割結果を[リスト]で返す

まずは簡単な文字列で実行例を確認してみましょう。

# デフォルト設定(空白文字)で分割
text = "ABC DEF\tGEH\nIJK"  # \t:タブ, \n:改行
text.split()
# ['ABC', 'DEF', 'GEH', 'IJK']

# 区切り文字を指定して分割
text = "ABC, DEF, GEH, IJK"
text.split(",")
# ['ABC', ' DEF', ' GEH', ' IJK']

簡単に文字列が分割できましたね。

結果はリストで返されるので、必要な要素を抽出するなどして使用していきましょう。

分割回数の指定

デフォルトでは、文字列内にある全ての区切り文字で分割が行われます。

引数maxsplitで、区切り文字で何回まで分割するかを指定することができます。

  • maxsplit = 最大の分割回数

指定した回数以上は分割されなくなります。1回だけ分割したいときに便利ですね。

text = "ABC DEF GEH IJK"

text.split(maxsplit=1)
# ['ABC', 'DEF GEH IJK']

text.split(maxsplit=2)
# ['ABC', 'DEF', 'GEH IJK']

rsplit()で右側から分割

split()の派生形として、rsplit()があります。

これは、文字列の右側から文字列の分割を行うメソッドです。

回数を指定せずに文字列を分割するだけの場合は、split()rsplit()で違いはありません。

分割回数を指定すると、split()rsplit()で挙動の違いが出ます。

text = "ABC DEF GEH IJK"

text.split(maxsplit=1)
# ['ABC', 'DEF GEH IJK']

text.rsplit(maxsplit=1)
# ['ABC DEF GEH', 'IJK']

次のように使い分けをするといいですね。

  • 左側からn回分割したいとき:split(maxsplit=n)
  • 右側からn回分割したいとき:rsplit(maxsplit=n)

maxsplitを指定しないと、全ての区切り文字で分割されるので、両者で同じ結果になってしまいます。

行ごとに分割|splitlines()

複数行のテキストを行ごとに分割する場合は、splitlines()を使うと便利です。

  • splitlines() ⇒ 文字列を改行文字('\n'または'\r\n')で分割

デフォルトでは、分割後に改行文字は残りません。

text = """一行目の文章です。
二行目です。
三行目。"""

# 改行文字を表示すると次のような状態
# '一行目の文章です。\n二行目です。\n三行目。'

# splitlines()で分割
text.splitlines()
# ['一行目の文章です。', '二行目です。', '三行目。']

改行文字を残しておきたい場合には、引数keepends=Trueを指定します。

text = """一行目の文章です。
二行目です。
三行目。"""

# splitlines()で分割(デフォルトでは改行文字は残らない)
text.splitlines()
# ['一行目の文章です。', '二行目です。', '三行目。']

# keepends=Trueで改行文字を残す
text.splitlines(keepends=True)
# ['一行目の文章です。\n', '二行目です。\n', '三行目。']

改行文字が分割後のテキスト内に残っていますね。

splitlines()は、テキストファイルの読み込み後に使用することが多いですね。

テキストファイルの読み込みについては、次の記事を参考にしてください

≫Python テキストファイル読み込み・テキスト処理まとめ
Pythonでは簡単にテキストファイルを読み込むことができます。しかし、実際にテキストファイルを読み込もうとすると、次のような問題に直面することも…①ファイルの読み込みってどうやるんだっけ?②エンコーディングのエラーがでた!③テキストの出力方法にも種類があって、使い分けがよくわからない!そこで、この記事ではPythonでのテキストファイル読み込み、よく使うテキスト処理について、図解付きで徹底解説しています!
www.yutaka-note.com/entry/bf_openfile
 

partition()メソッドでの分割

partition()メソッドは、指定した区切り文字で文字列を前後に分割して、区切り文字を含めた分割結果を返します。

  • text.partition("区切り文字")("分割結果1", "区切り文字", "分割結果2")

区切り文字を含めて、文字列を別途処理する必要がある場合に使うと便利です。

text = "12345X54321"

text.partition("X")
# ('12345', 'X', '54321')

分割回数を指定することはできず、初めに出現した区切り文字で分割されます。

text = "12345X54321"

text.partition("5")
# ('1234', '5', 'X54321')

rpartition()を使用すると、右側から分割することができます。

text = "12345X54321"

text.rpartition("5")
# ('12345X', '5', '4321')

正規表現で分割|reモジュール

Pythonの正規表現モジュールreを使用すると、正規表現で区切り文字を指定できます。

正規表現は慣れるまではとっつきにくいですが、使えるようになると文字列操作がぐっと楽になります。

  • re.split("区切り文字の正規表現", "分割する文字列")

正規表現のパターンの作成方法、正規表現を使用した文字列の検索方法については次の記事で解説しています。

≫Python正規表現パターンを図解&サンプルで本当にわかりやすく解説
Pythonのreモジュールで正規表現パターンを作成する際に重要な3つのポイントを解説します。①特定の文字の繰り返しを指定 ②数値や文字、空白など条件による文字条件の指定 ③グループ化によるパターン内の特定部分の抽出
www.yutaka-note.com/entry/regex_01
 

以下では、分割で特に便利な正規表現に絞って紹介します(詳しくは、上の記事を見てください)。

分割で便利な正規表現

文字列の分割のために覚えておくと便利な正規表現を紹介します。

分割方法 説明
複数の区切り文字を指定 |を使用して複数の文字を区切り文字に指定
*例:"aaa\|bbb"aaaまたはbbbで分割される。
数値 or 数値以外で分割 \d+: → 1個以上の数値で分割
\D+: → 1個以上の数値以外で分割
空白文字で分割 \s+ → 1個以上の空白文字で分割
*空白文字:スペース、タブ、改行など" \t\n\r\f\v"
import re
text = "1999年1月2日"

# |を使用して、複数の文字列を区切り文字に指定
re.split("年|月|日", text)
# ['1999', '1', '2', '']

# 数値で分割
re.split("\d+", text)
# ['', '年', '月', '日']

# 数値以外で分割
re.split("\D+", text)
# ['1999', '1', '2', '']

正規表現が使えれば、いろいろな文字列分割ができますね。

オススメ|データ分析の勉強方法

今回はPythonでテキストファイルを分割する方法ついて解説しました。

データ分析初心者の方にはこちらの記事もおススメです。

≫独学でデータ分析を勉強するオススメ学習本
独学でのpythonデータ分析勉強に役立ったおススメ書籍を紹介していきます。業務でそれなりにデータ分析を行えるまで、いろいろな試行錯誤をしてきましたが、もし自分が今ゼロから勉強する立場ならどうするのがいいのか考えてみました。以下では、入門書、個別モジュール用、実践用の3つの視点でおススメ本を紹介していきます。
www.yutaka-note.com/entry/data_analysis
 

Pythonで作業の効率化を目指している方には、次の記事がおススメです。

≫【レビュー】退屈なことはPythonにやらせよう | 単純作業は自動化
名著「退屈なことはPythonにやらせよう 」をレビュー!単純作業を自動化して、自分が本当にしたいことのために時間を確保していこう!所要時間やできるようになったことを紹介!この本を参考にして作成した、自作自動化スクリプトも紹介しています!
www.yutaka-note.com/entry/python_jido
 
≫【レビュー】Python自動処理 全部入り。 | 即効性重視!Pythonによる自動化事例集!
今回は、Pythonの作業自動処理の解説本『Python自動処理全部入り』をレビュー!この本は、特定の作業に特化した即効性の重視の自動化解説本です。よくある作業から自動化しやすい作業を抽出して、それらの自動化を集中的に解説した本という印象です。本の中に自分のニーズに合った自動化処理が見つかれば、その日からすぐ使える事例集のような印象です。
www.yutaka-note.com/entry/python_jido_zenbu
 

Python フォルダ内ファイル検索方法まとめ|pathlib.glob

Pythonのファイル操作用のモジュールpathlibというものがあります。

pathlibは、一連のファイル操作を一つのモジュールでこなせる非常に強力なモジュールです。

.glob()メソッドを使用すれば、様々な条件でのファイル・フォルダの検索を行うことが可能です。

しかし、実際にファイル検索する際には、次のような問題に直面することも:

  • pathlibを使って、ファイル検索するには具体的にどうすればいいの?
  • 特定の条件に合ったファイルを検索する方法は?
  • サブフォルダも含めて、全ファイル検索するにはどうするの?

こういった疑問を解決するために、この記事ではpathlib.glob()メソッドを使用したファイル検索方法について、図解付きで解説していきます。

フォルダ内ファイル検索の基本手順

この記事では、pathlibモジュールのPathを使用したファイル内検索方法について紹介します。

ファイル検索の基本手順は次の通りです。

手順 スクリプトの例
1. pathlibからPathクラスをインポート from pathlib import Path
2. 検索対象フォルダのPathオブジェクト作成 Path("検索フォルダ")
3. 条件に合ったファイル・フォルダを検索 Path.glob("検索条件")

Path.glob("検索条件")は、検索結果のジェネレータを返すので必要に応じて[リスト化]したり、for文を回したりします。

まずは実行例を紹介して、ファイル検索が想像以上に簡単だということをお伝えしたいと思います。

フォルダ内ファイル検索の実行例

例として、次のようなファイル構造を考えてみます。

Path.glob("検索条件")で、base_folder内の全てのファイル、フォルダを表示させてみましょう。

# pathlibからPathクラスをインポート
from pathlib2 import Path

# 検索対象フォルダのPathオブジェクト作成
BASE_FOLDER = Path("base_folder")

# 結果をfor文でprint表示するパターン
for p in BASE_FOLDER.glob("*"):
    print(p)
    
# base_folder\sample1.txt
# base_folder\sample2.txt
# base_folder\sub_folder1

# 結果をListにまとめるパターン
list(BASE_FOLDER.glob("*"))
# [WindowsPath('base_folder/sample1.txt'),
#  WindowsPath('base_folder/sample2.txt'),
#  WindowsPath('base_folder/sub_folder1')]

検索結果は、実行環境に応じたPathクラスとして返されます。

上記の例では、「任意のファイル・フォルダ」という検索条件を"*"という特殊文字で表現しています。

.glob()メソッドでは、特殊文字を上手に利用して検索条件を設定することがポイントです。

次に、どのような特殊文字が使用できるのか詳しく見ていきたいと思います。

検索で使用できる特殊文字

検索で使用できる特殊文字は、次の通りです。

特殊文字 意味 適用
* 0文字以上の任意の文字列 あらゆる文字(ワイルドカード)
? 1文字以上の任意の文字列  
[文字] [文字]内の文字 例)[ab]:a or b
[a-z]:a, b, c,…, z
[0-9]: 0, 1, 2,…, 9
[!文字] [!文字]内の文字以外 例)[!ab]:a or b以外
[!a-z]:a, b, c,…, z以外
[!0-9]: 0, 1, 2,…, 9以外
** Pathのフォルダとサブフォルダ **は、他の特殊文字と意味合いが異なる
検索範囲をPathフォルダと、そのサブフォルダにする

さらに追加で覚えておきたいポイントは、次の通りです。

ポイント 適用
特殊文字は、普通の文字と組合せ可 Ex) "012_*.txt"
  -> 「012_file_name.txt」や「012_ファイル.txt」にマッチ
正規表現のような
繰り返しを表す特殊文字はない
Ex) 「01.txt」, 「12.txt」の両方にマッチするには、
  ->"[0-9][0-9].txt"とする
サブフォルダはフォルダ名/で指定 Ex) sub_folder/*.txt
  ->「sub_folder内のtxtファイル」にのみマッチ

特殊文字は、正規表現と類似していますが、正規表現の全ての特殊文字を使えるわけではない点に注意ですね。

検索パターンテンプレ紹介

これらのポイントを踏まえたうえで、使用頻度が高そうな検索パターンを個別に紹介していきます。

検索内容 パターンテンプレ
フォルダ内の全ファイル・フォルダ Path.glob("*")
サブフォルダ内の全ファイル・フォルダ Path.glob("*/*")
サブフォルダも含めた全ファイル・フォルダ Path.glob("**")
特定の拡張子 Path.glob("*.拡張子")
Ex) ・Path.glob("*.txt")
      ・Path.glob("*.csv")
特定の文字で始まる Path.glob("特定の文字*")
Ex) ・Path.glob("2020*")
      ・Path.glob("sample*")
フォルダのみ抽出 Path.glob("検索条件")
+ Path.is_dir()組み合わせ
ファイルのみ抽出 Path.glob("検索条件")
+ Path.is_file()組み合わせ

これらのパターンについて、サンプルコードを紹介します。

ここでも次のようなファイル構造を想定してみます。

フォルダ内の全ファイル・フォルダ抽出

全ファイル、フォルダを抽出する場合は、検索条件に任意の文字列を意味する特殊文字"*"を指定します。

  • Path.glob("*")

これは、記事冒頭で紹介したサンプルと同じですね。

特殊文字*の検索範囲の意味をサンプルデータの例と一緒に図にしてみましょう。

サンプルデータで挙動を再確認してみましょう。

# 結果をfor文でprint表示するパターン
for p in BASE_FOLDER.glob("*"):
    print(p)
# base_folder\sample1.txt
# base_folder\sample2.txt
# base_folder\sub_folder1

# 結果をListにまとめるパターン
list(BASE_FOLDER.glob("*"))
# [WindowsPath('base_folder/sample1.txt'),
#  WindowsPath('base_folder/sample2.txt'),
#  WindowsPath('base_folder/sub_folder1')]

検索条件の意味がわかってくると、ファイル・フォルダの検索がとても簡単なことに気づきますね。

サブフォルダ内の全ファイル・フォルダ抽出

サブフォルダ内のファイル・フォルダを抽出する方法を紹介します。

この場合は、検索条件の始めにサブフォルダを意味する*/を追加します。

  • Path.glob("*/*")

検索条件*/*の意味を、サンプルデータの例と一緒に図にまとめてみます。

サンプルデータで挙動を再確認してみましょう。

# 結果をfor文でprint表示するパターン
for p in BASE_FOLDER.glob("*/*"):
    print(p)
# base_folder\sub_folder1\sample1_1.txt

# 結果をListにまとめるパターン
list(BASE_FOLDER.glob("*/*"))
# [WindowsPath('base_folder/sub_folder1/sample1_1.txt')]

"~/"を「~の中の」と解釈すると、検索条件の意味がよくわかってきますね。

特定のフォルダを指定したい場合は、次のように使うこともできます。

  • "特定のフォルダ名/検索条件"

Pathフォルダ+サブフォルダで全抽出

サブフォルダも含めて、全ファイル・フォルダを検索する場合を紹介します。

この場合は、検索条件の始めに「Pathフォルダとサブフォルダ」を意味する特殊文字**を追加します。

  • Path.glob("**/*")

ここで、**/*は、次のような意味になっています。サンプルデータの例とともに図示してみます。

サンプルデータで挙動を再確認してみましょう。

# 結果をfor文でprint表示するパターン
for p in BASE_FOLDER.glob("**/*"):
    print(p)
# base_folder\sample1.txt
# base_folder\sample2.txt
# base_folder\sub_folder1
# base_folder\sub_folder1\sample1_1.txt

# 結果をListにまとめるパターン
list(BASE_FOLDER.glob("**/*"))
# [WindowsPath('base_folder/sample1.txt'),
#  WindowsPath('base_folder/sample2.txt'),
#  WindowsPath('base_folder/sub_folder1'),
#  WindowsPath('base_folder/sub_folder1/sample1_1.txt')]

**は、正規表現にもなく、ファイル検索専用の特殊文字で、初めはとっつきにくい部分がありますが…。

Pathフォルダとサブフォルダ」という意味だと理解すれば、使い勝手のいい強力な特殊文字ですね。

特定の拡張子で抽出

特定の拡張子のファイルを抽出する場合は、特殊文字"*"と拡張子を組み合わせます。

  • Path.glob("*.拡張子")
    • Ex1) txtファイルの場合 -> Path.glob("*.txt")
    • Ex2) csvファイルの場合 -> Path.glob("*.csv")

ここで、"*.拡張子"は次のような意味です。.txtの例と一緒に確認してみましょう。

これは使用頻度が高いので、サブファルダ内、Pathフォルダおよびサブフォルダ内全てを検索する場合も含めて表に整理してみます。

検索先 パターンテンプレ
Pathフォルダ内 Path.glob("*.拡張子")
サブフォルダ内 Path.glob("*/*.拡張子")
Pathフォルダ+サブフォルダ内 Path.glob("**/*.拡張子")

サンプルデータで挙動を再確認してみましょう。

# Pathフォルダ内を検索
list(BASE_FOLDER.glob("*.txt"))
# [WindowsPath('base_folder/sample1.txt'),
#  WindowsPath('base_folder/sample2.txt')]

# Pathフォルダ+サブフォルダ内を検索
list(BASE_FOLDER.glob("**/*.txt"))
# [WindowsPath('base_folder/sample1.txt'),
#  WindowsPath('base_folder/sample2.txt'),
#  WindowsPath('base_folder/sub_folder1/sample1_1.txt')]

特定の文字列で始まる

特定の文字列で始まるファイル・フォルダを抽出する場合は、検索対象の文字列と特殊文字"*"を組み合わせます。

  • Path.glob("特定の文字*")
    • Ex1) "2020"から始まる場合 -> Path.glob("2020*")
    • Ex2) "sampleから始まる場合 -> Path.glob("sample*")

サブファルダ内、Pathフォルダおよびサブフォルダ内全てを検索する場合も含めて表に整理してみます。

検索先 パターンテンプレ
Pathフォルダ内 Path.glob("特定の文字*")
サブフォルダ内 Path.glob("*/特定の文字*")
Pathフォルダ+サブフォルダ内 Path.glob("**/特定の文字*")

サンプルデータで挙動を再確認してみましょう。

# Pathフォルダ内を検索
list(BASE_FOLDER.glob("sample1*"))
# [WindowsPath('base_folder/sample1.txt')]

# Pathフォルダ+サブフォルダ内を検索
list(BASE_FOLDER.glob("**/sample1*"))
# [WindowsPath('base_folder/sample1.txt'),
#  WindowsPath('base_folder/sub_folder1/sample1_1.txt')]

フォルダのみ・ファイルのみを抽出

フォルダのみ、ファイルのみを抽出する方法を紹介します。

Pathオブジェクトには、Pathがフォルダなのか、ファイルなのか判定するメソッドがあります。

判定内容 メソッド名 結果
フォルダかどうか .is_dir() True or False
ファイルかどうか .is_file() True or False

Path.glob()の結果もPathオブジェクトなので、if文と組み合わせてフォルダ・ファイルの判定が可能です。

まずはフォルダのみを抽出する例を見てみましょう。

# 結果をfor文でprint表示するパターン
for p in BASE_FOLDER.glob("*"):
    if p.is_dir():
        # 処理内容
        print(p)
# 実行結果
# base_folder\sub_folder1

# 結果をListにまとめるパターン
[p for p in BASE_FOLDER.glob("*") if p.is_dir()]
# [WindowsPath('base_folder/sub_folder1')]

次にファイルのみを抽出する例を見てみましょう。

# 結果をfor文でprint表示するパターン
for p in BASE_FOLDER.glob("*"):
    if p.is_file():
        # 処理内容
        print(p)
# 実行結果
# base_folder\sample1.txt
# base_folder\sample2.txt

# 結果をListにまとめるパターン
[p for p in BASE_FOLDER.glob("*") if p.is_file()]
# [WindowsPath('base_folder/sample1.txt'),
#  WindowsPath('base_folder/sample2.txt')]

ここまで覚えると、ファイル検索がかなり自由にできるようになってくると思います。

様々な条件を組み合わせて自由にファイル検索を行いましょう。

より複雑なファイル検索をしたい場合は、次に紹介する正規表現との組み合わせも選択肢の一つです。

正規表現による検索条件設定

.glob()メソッドで使用できる特殊文字は、**, *, ?, [], [!]のみでした。

正規表現とは異なり、繰り返しを表現する特殊文字がないところが難点です。

繰り返しを含めたより複雑な条件で検索したい場合には、素直に正規表現と組み合わせると良いです。

例えば、次のようなフォルダを考えてみましょう。

この中から1995~2000のファイルを検索したい場合は、どうしたら良いでしょうか?

正規表現を使えば簡単ですね。

import re
BASE_FOLDER2 = Path("base_folder2")

data_1995_to_2000 = []
for p in BASE_FOLDER2.glob("*.csv"):
    # 正規表現でPathから数字4ケタ取得
    res = re.search("\d{4}", str(p))
    year = int(res.group())
    
    # 数字4ケタが条件を満たすか判定
    if 1995 <= year <=2000:
        data_1995_to_2000.append(p)
        
print(data_1995_to_2000)
# [WindowsPath('base_folder2/1995_Data.csv'),
#  WindowsPath('base_folder2/2000_Data.csv')]

この例では、ファイル数が少なかったですが、無数のファイルの中からデータ検索する場合を考えると正規表現の重要性がわかりますね。

  • 【参考】pythonの正規表現については、次の記事で詳しく解説しています。
≫Python正規表現パターンを図解&サンプルで本当にわかりやすく解説
Pythonのreモジュールで正規表現パターンを作成する際に重要な3つのポイントを解説します。①特定の文字の繰り返しを指定 ②数値や文字、空白など条件による文字条件の指定 ③グループ化によるパターン内の特定部分の抽出
www.yutaka-note.com/entry/regex_01
 
≫Python 正規表現の検索を本当にわかりやすく解説
Pythonで正規表現を使用して、パターン検索する方法をわかりやすく解説しています。① 検索メソッド(関数)の実行方法② 各メソッドの紹介(単独検索search()、全体検索findallなど)③ 検索結果の出力であるMatchオブジェクトの内容
www.yutaka-note.com/entry/regex_02
 

pathlibとpathlib2どっちを使う?

最後にpathlibモジュールについて、補足事項です。

pathlib関連のモジュールには、pathlibpathlib2の2種類があります。

pathlib2≫ドキュメントを参考に、2つの違いを簡単にまとめると次の通りです。

モジュール インストール要否 モジュールメンテナンス
pathlib Python標準モジュールなので不要 メンテナンスされていない
pathlib2 pipcondaでインストール必要 継続してメンテナンスされている

メンテナンスが継続されているという点から、私は基本的にpathlib2を使用しています。

(ちなみに、condaでいろいろモジュールをインストールしていると、いつの間にかpathlib2がインストールされていることも多いです。)

pipおよびcondapathlib2をインストール際のコマンドは次の通りです。

pip install pathlib2
conda install pathlib2

Pythonで作業を効率化する方法

今回はpathlib.glob()メソッドでファイル検索する方法を解説しました。

Pythonを使えば、簡単なスクリプトで作業を劇的に効率化することも可能です!

Pythonの効率化関係で言えば、次の記事で紹介しているような本が自動化の本がおススメです。

≫【レビュー】退屈なことはPythonにやらせよう | 単純作業は自動化
名著「退屈なことはPythonにやらせよう 」をレビュー!単純作業を自動化して、自分が本当にしたいことのために時間を確保していこう!所要時間やできるようになったことを紹介!この本を参考にして作成した、自作自動化スクリプトも紹介しています!
www.yutaka-note.com/entry/python_jido
 
≫独学でデータ分析を勉強するオススメ学習本
独学でのpythonデータ分析勉強に役立ったおススメ書籍を紹介していきます。業務でそれなりにデータ分析を行えるまで、いろいろな試行錯誤をしてきましたが、もし自分が今ゼロから勉強する立場ならどうするのがいいのか考えてみました。以下では、入門書、個別モジュール用、実践用の3つの視点でおススメ本を紹介していきます。
www.yutaka-note.com/entry/data_analysis
 

Python正規表現パターンを図解&サンプルで本当にわかりやすく解説

正規表現を使うと、「文字列の中から自分で設定したパターンの文字列を探すこと」ができます。

正規表現が便利なことはわかっていても、よくわからない記号がたくさんあったり、うまくマッチしなかったり、なかなか手を出せないものです。

この記事では、Pythonの正規表現用モジュールreに焦点をあてて、正規表現のパターン作成をわかりやすく解説します!

  • 基本的な正規表現パターンの作成
  • 正規表現の特殊文字の意味(特定の文字種や繰り返し)
  • パターン内のグループ化(特定部分の抽出)、A or Bの設定
  • 先読み/後読み・肯定/否定

正規表現パターンの作成方法を覚えて、正規表現を使いこなせるようになりましょう!

正規表現の使い方イメージ

まずは、正規表現による文字の抽出例を見て、正規表現のイメージをつかみましょう。

次のような、日付を含んだ例文を考えてみます。

  • 私は4月1日は焼き肉を食べて、4月2日は寿司を食べて、4月13日はタラコを食べた。

この文字列から、×月×日というパターンの日付だけ抽出してみます。

import re

src = "私は4月1日は焼き肉を食べて、4月2日は寿司を食べて、4月13日はタラコを食べた。"
re.findall(r"\d月\d{1,2}日", src) #\dは数字を表す特殊文字

# ['4月1日', '4月2日', '4月13日']

ここでは、×月×日を"\d月\d{1,2}日"という正規表現のパターンにして文章の中から探しています。

図にすると次のようなイメージでにパターンを探しています。

テキストのパターンを見抜けば、様々なデータの抽出が可能です。

上の例では、「×月×日は○○○を食べ」というパターンで日付と食べ物が記述されているようです。

食べ物も抽出してみましょう。

re.findall(r"(\d月\d{1,2}日)は(\w+?)を食べ", src)
# [('4月1日', '焼き肉'), ('4月2日', '寿司'), ('4月13日', 'タラコ')]

日付と食べ物が抽出できました。

正規表現を覚えれば、簡単にデータの抽出、削除、置換ができるようになります。

この記事では、pythonの正規表現ライブラリreに焦点をあてて正規表現パターンの作成方法を解説していきます!

正規表現用ライブラリ|re

pythonでは、標準ライブラリreを使用して正規表現を取り扱うことが出来ます。

標準ライブラリなので、外部モジュールのインストールといった環境構築の必要はなく、import文だけで使用できるようになります。

import re

以下で正規表現の基本的パターンの生成方法について確認していきましょう。

正規表現パターンの作り方(3つのポイント)

正規表現では、特定の文字のパターンを作成して、パターンにマッチする文字部分を検索します。

パターンを作成するときは、次の3つの概念が重要になります。

  1. 特定の文字の繰り返しを指定
  2. 数値や文字、空白など条件による文字条件の指定
  3. グループ化によるパターン内の特定部分の抽出

例えば、正規表現で「数字の後に円」が来るパターンを表現すると次のように書けます。

  • "(\d+)円"

これは、次のような条件の組み合わせです。

  • 「数字(文字条件"\d")を一回以上(繰り返し条件"+")」
  • その後に「円」が続く

さらに、数字部分"\d""()"で囲んでグループ化しておくと、後から数字だけ抽出できます。

以下で、文字条件、繰り返し条件、グループ化を詳しく見ていきましょう。

文字の繰り返し

繰り返し回数を指定すると、直前の文字を何回繰り返すかを指定できます。

繰り返し回数の指定方法は、次の2つの方法があります。

  • 数字による繰り返し回数の指定"{n,m}"
  • 特殊文字による繰り返し回数の指定"*+?"

2つの指定方法について、順番に見ていきましょう。

繰り返し回数指定{n,m}

数値での指定は{}に繰り返し回数を入力します。

記法 意味
{n} n回繰り返す
{n,m} n回~m回繰り返す
{n,} n回以上繰り返す
{,m} 0~m回まで繰り返す

こうすると、次のように似たようなパターンを一つの正規表現パターンで記述できます。

  • "gre{2,5}n""e""2~5"回まで繰り返すパターン
サンプル文字列 マッチするかどうか コメント
"grn" × "e""0"回なので
"green" "e""2"回なので
"greeeeen" "e""5"回なので
"greeeeeeeeeen" × "e""10"回なので

実際に正規表現用ライブラリreで実行例を見てみましょう。

re.match("gre{2,5}n", "grn")
# None
re.match("gre{2,5}n", "green")
# <re.Match object; span=(0, 5), match='green'>
re.match("gre{2,5}n", "greeeeen")
# <re.Match object; span=(0, 8), match='greeeeen'>
re.match("gre{2,5}n", "greeeeeeeeeen")
# <re.Match object; span=(0, 8), match='greeeeen'>
# None

手入力で繰り返し回数を指定する以外にも、特殊文字でよく使う繰り返し回数を指定できます。

既定の繰り返し回数*+?

{n,m}の形式では、手入力で繰り返し回数を指定することができました。

ありがたいことに、よく使う繰り返し回数は、特殊文字で指定することができます。

繰り返しに関する特殊文字は下表の通りです。

特殊文字 意味 ざっくりした使い道
? 0~1回以上繰り返す 直前の文字があってもなくてもいいとき
* 0回以上繰り返す 直前の文字が何回あってもなくてもいいとき
+ 1回以上回繰り返す 直前の文字が少なくとも1回は必要なとき

それぞれ次のような使い方ができます。

特殊文字 使い方例 マッチ例
? "https?" "http", "https"("s"はあってもなくてもOK)
* "Yes!*" "Yes", "Yes!", "Yes!!!""!"は何回あってもなくてもOK)
"+" "\d+日" "1日", "10日", "99日"(少なくとも数字\dが1回は必要)

実際に正規表現用ライブラリreで実行例を見てみましょう。

# "?":0~1回以上繰り返す
re.match("https?", "http")
# <re.Match object; span=(0, 4), match='http'>
re.match("https?", "https")
# <re.Match object; span=(0, 5), match='https'>
 
# "*":0回以上繰り返す
re.match("Yes!*", "Yes")
# <re.Match object; span=(0, 3), match='Yes'>
re.match("Yes!*", "Yes!!!")
# <re.Match object; span=(0, 6), match='Yes!!!'>

# "+":1回以上繰り返す
re.match("\d+日", "1日")
# <re.Match object; span=(0, 2), match='1日'>
re.match("\d+日", "99日")
# <re.Match object; span=(0, 3), match='99日'>
re.match("\d+日", "日")
# <re.Match object; span=(0, 3), match='99日'>
# None  ←数字が1回以上必要

"?", "*", "+"は、よくわからない変な記号ではなくて、繰り返し回数を一文字で指定できる便利な記号だったんですね!

文字の集合の表現

正規表現パターン作成の重要なもう一つの要素が文字集合です。

例えば、次の例の「 」内の文字のように特定の文字の集合を指定できます。

  • 「月、火、水、木、金」+ 曜日
  • 「数字」 + 日
  • 「名前」 + さん

文字集合でパターン生成すると、様々なパターンの検索ができるようになります。

文字集合を指定[]

文字集合を自分で指定する場合は、"[]"内に文字を並べます。

特殊文字 含まれる文字の集合
"[]" "[ ]"内で文字集合の要素を指定
"[^]" "[^ ]"内の要素以外にマッチ

例えば、次のように使用します。

  • "[ABCDE]""A", "B", "C", "D", "E"にマッチ
  • "[12345]""1", "2", "3", "4", "5"にマッチ
  • "[月火水木金]""月", "火", "水", "木", "金"にマッチ

連続する文字の場合は、次のように-で省略表記することもできます。

  • "[A-E]""A", "B", "C", "D", "E"にマッチ
  • "[1-5]""1", "2", "3", "4", "5"にマッチ

実際に正規表現用ライブラリreで実行例を見てみましょう。

re.match("[月火水木金]曜日", "月曜日")
# <re.Match object; span=(0, 3), match='月曜日'>
re.match("[月火水木金]曜日", "日曜日")
# None ←日曜日は含まれていない

re.match("[1-5]時間", "1時間")
# <re.Match object; span=(0, 3), match='1時間'>
re.match("[1-5]時間", "9時間")
# None ←9は含まれていない

"^"を付けて要素を指定すると、指定した要素以外にマッチするようになります。

特定の文字を除外したいときに便利ですね。

ただし、指定していない文字全てにマッチする点には注意が必要です。

re.match(r"[^土日]曜日","火曜日" )
# <re.Match object; span=(0, 1), match='火曜日'>
re.match(r"[^土日]曜日","炎曜日" )
# <re.Match object; span=(0, 3), match='炎曜日'>

文字集合を表す特殊文字.\w\d\s

正規表現で頻繁に使用される文字集合の特殊文字は次の通りです。

特殊文字 含まれる文字の集合 コメント
"." 任意の文字(ワイルドカード) 改行は含まない。改行を含む場合は、"re.DOTALL"指定
"\w" 単語文字(英数字・日本語などのこと) 全角文字等も含む。"re.ASCII"でASCIIに限定可能
"\W" 単語文字以外 同上
"\d" 数字 全角数字等も含む。"re.ASCII"でASCIIに限定可能
"\D" 数字以外 同上
"\s" 空白文字(スペース、タブ、改行など) "[ \t\n\r\f\v]"
"\S" 空白文字以外  

もちろん繰り返しを表す"{}", "+", "*", "?"と組み合わ可能なので、様々なパターンが自由自在に作れます。

使用例を見てみましょう。

特殊文字 使用例 コメント
"." "<p>.*</p>" 特定の文字に挟まれた任意の文字抽出(ここではpタグの例)
"\w" "\w曜日" 曜日の抽出
"\d" "\d+月\d+日" 日にちの抽出

実際に正規表現用ライブラリreで実行例を見てみましょう。

re.match("<p>.*</p>", "<p>こんにちは</p>")
# <re.Match object; span=(0, 12), match='<p>こんにちは</p>'>

re.match("\w曜日","月曜日" )
# <re.Match object; span=(0, 3), match='月曜日'>

re.match("\d+月\d+日","10月20日" )
# <re.Match object; span=(0, 6), match='10月20日'>

任意文字.に改行を含める方法

注意点としては、任意の文字列の特殊文字"."は、改行は含まないという例外があります。

例えば、次のテキストで「」内文字列を".+"で抽出しようとしても、改行があるのでうまくいきません。

text="""
田中「こんにちは。
私は元気です。
あなたはどう?」
"""

re.search("「.+」", text)
# None

任意の文字列"."に、改行を含ませるには"re.DOTALL"フラグを有効にします。

re.search("「.+」", text, re.DOTALL)
# <re.Match object; span=(3, 27), match='「こんにちは。\n私は元気です。\nあなたはどう?」'>

貪欲マッチと非貪欲マッチ+?*?

繰り返し回数指定と文字集合を組み合わせるときは、貪欲、非貪欲という概念が重要になります。

通常、正規表現は結果が最長になるようにマッチ(貪欲マッチ)しようとします。

例として、次のテキストで「」に囲まれた文字列を抽出してみます。

text = """
田中「こんにちは」吉田「さようなら」
"""
re.search("「.+」", text)
# <re.Match object; span=(3, 19), match='「こんにちは」吉田「さようなら」'>

「こんにちは」ではなく、「こんにちは」吉田「さようなら」が抽出されています。

結果が最長になるようにマッチ(貪欲マッチ)したため、一回目の"「"と、二回めの"」"にマッチしてしまいました。

最小の長さでマッチ(非貪欲マッチ)させるには、繰り返しの特殊文字"*", "+"の後に"?"を追加します。

text = """
田中「こんにちは」吉田「さようなら」
"""
re.search("「.+?」", text)
# <re.Match object; span=(3, 10), match='「こんにちは」'>

貪欲マッチと非貪欲マッチを整理すると、下表のようになります。

マッチ対象 貪欲マッチ(最長) 非貪欲マッチ(最短)
任意文字列"0"回以上繰り返し ".*" ".*?"
任意文字列"1"回以上繰り返し ".+" ".+?"

グループ化

正規表現パターン作成の重要な要素の一つがグループ化です。

グループ化を行うと、次のようなことができます。

  • 各グループの内容を個別に抽出
  • 置換時にグループ内の内容を再利用
  • AまたはBの指定

まずはグループ化の方法を確認していきましょう。

グループ作成方法

グループを作成するには、パターン内でグループにしたい部分を"()"で囲みます。

m = re.match("商品名:(\w+), 価格:(\d+)円", "商品名:コーヒー, 価格:100円")
# <re.Match object; span=(0, 17), match='商品名:コーヒー, 価格:100円'>
# 商品名と価格を()で囲んで、それぞれグループ化済み

続いてグループ化したマッチ結果の利用方法を見ていきましょう。

グループの内容を個別抽出

"Matchオブジェクト"のメソッドを使用して、各グループの内容を抽出することができます。

グループ抽出メソッド メソッド内容
.groups() 各グループを要素として持つ"タプル"
.group() 引数で指定したグループ内容を出力

まずは、.groups()メソッドで各グループの内容を確認してみます。

m.groups()
'商品名:コーヒー, 価格:100円'

個別に出力する場合は、".group()"メソッドを使用します。

グループには、"("が現れる順番に1から連番が振られています。

引数を省略するか、0を入力すると、マッチした内容全体に出力されます。

# 引数0または省略で、全体を出力
m.group()

# 引数1で一つ目のグループ内容
m.group(1)
# 'コーヒー'

# 引数2で一つ目のグループ内容
m.group(2)
# '100'

グループに名前設定

次のようにグループに名前をつけることも可能です。

  • "(?P<name>...)"

実際に正規表現用ライブラリreで実行例を見てみましょう。

m = re.match("商品名:(?P<prod_name>\w+), 価格:(?P<prod_price>\d+)円", "商品名:コーヒー, 価格:100円")
# <re.Match object; span=(0, 17), match='商品名:コーヒー, 価格:100円'>

名前を指定しておけば、.group()メソッドの引数で名前を使用することも可能です。

# 引数1で一つ目のグループ内容
m.group("prod_name")
# 'コーヒー'

# 引数2で一つ目のグループ内容
m.group("prod_price")
# '100'

マッチ内容を利用して置換

正規表現を使用した文字列置換時には、パターン内のグループ要素を置換時に再利用することも可能です。

置換後のテキスト内で次のように指定します。

指定方法 指定例
"\グループ番号" "\1", "\2", ...
"\g<グループ番号>" "\g<1>", "\g<2>", ...
"\g<グループ名>" "\g<グループ名1>", "\g<グループ名2>",...

実際に正規表現用ライブラリreで実行例を見てみましょう。

re.sub("商品名:(\w+), 価格:(\d+)円", #置換対象のパターン
       r"Name: \1, price: \g<2>", # 置換後の内容
       "商品名:コーヒー, 価格:100円") # 置換されるテキスト
# 'Name: コーヒー, price: 100'
re.sub("商品名:(?P<prod_name>\w+), 価格:(?P<prod_price>\d+)円", #置換対象のパターン
       r"Name: \g<prod_name>, price: \g<prod_price>", # 置換後の内容
       "商品名:コーヒー, 価格:100円") # 置換されるテキスト
# 'Name: コーヒー, price: 100'

単語Aまたは単語B(ORの組み込み)

「単語Aまたは単語B」のOR条件を指定する場合は、"|"で単語を繋ぎます。

  • "単語A|単語B" ← 単語Aまたは単語Bにマッチ

ただし、実用上はORの前後にも何か文字があることがほとんどです。

その場合は、次のようにOR部分をグループ化します。

  • "これは(単語A|単語B)です" ←「これは単語Aです」または「これは単語Bです」にマッチ

実際に正規表現用ライブラリreで実行例を見てみましょう。

re.search("(python|ruby)経験あり", "python経験あり")
# <re.Match object; span=(0, 10), match='python経験あり'>

re.search("(python|ruby)経験あり", "ruby経験あり")
# <re.Match object; span=(0, 8), match='ruby経験あり'>

re.search("(python|ruby)経験あり", "調理師経験あり")
# None ←python or ruby にマッチしない

文頭/文末の指定^$

特殊文字"^", "$"を使用すると文頭/文末のみにマッチするという条件を追加することができます。

特殊文字 追加条件 書き方
^ 文頭でマッチ ^パターン
$ 文末でマッチ パターン$

ここで注意したいのは、"^", "$"は行ごとに文頭/文末の評価をするのではなく、文字列全体の先頭か末尾かの評価しかできません。

各行ごとに、文頭/文末のチェックを行うには、"re.MULTILINE"フラグを指定します。

次のサンプルテキストで実行例を見てみましょう。

text="""1月:寒い
2月:超寒い
3月:かなり寒い(2月よりはまし)
4月:暖かい
5月:まあまま暖かい(たまに8月並みのこともある)
6月:かなり暖かい
"""

まずは"^"について、"^"の有無、"re.MULTILINE"の有無による挙動の違いを確認してみましょう。

# ^がない場合
re.findall("\d月",text)
# ['1月', '2月', '3月', '2月', '4月', '5月', '8月', '6月']
# 文頭・文中関係なしに全ての月を抽出

# ^で先頭指定
re.findall("^\d月",text)
# ['1月']
# テキストの先頭の一個のみ抽出

# ^で文頭指定(re.MULTILINE)
re.findall("^\d月",text, re.MULTILINE)
# ['1月', '2月', '3月', '4月', '5月', '6月']
# re.MULTILINEフラグによって、各行の文頭の月に一致

次に"$"について、"$"の有無、"re.MULTILINE"の有無による挙動の違いを確認してみましょう。

# $がない場合
re.findall(":.*暖かい",text)
# [':暖かい', ':まあまま暖かい', ':かなり暖かい']
# 文頭・文中関係なしに全ての暖かいを抽出

# $で末尾指定
re.findall(":.*暖かい$",text)
# [':かなり暖かい']
# テキストの末尾の一個のみ抽出

# $で文末指定(re.MULTILINE)
re.findall(":.*暖かい$",text, re.MULTILINE)
# [':暖かい', ':かなり暖かい']
# re.MULTILINEフラグによって、各行の文末に一致するか判定
# 文末に余計なコメント(たまに8月並みのこともある)がある行はマッチしない

上記の例では、":"は抽出しないほうが自然ですね。

この場合は、次に紹介する後読み肯定を利用します。

先読み/後読み・肯定/否定(?…)

先読み/後読み・肯定/否定を指定すると、マッチ条件に「パターンの前後に特定の文字があるかないかを追加」できます。

先読み/後読み・肯定/否定で追加される条件は次の表の通りです。

先読み/後読み 肯定 否定
先読み:パターンの右側に… 特定の文字がある 特定の文字がない
後読み:パターンの左側に… 特定の文字がある 特定の文字がない

ポイントとしては、先読み/後読みの内容は、マッチ結果としては出力されません。

そのため、次のようなときに便利です。

  • マッチの条件としては、設定したいが…
  • 結果として出力したいわけではない

具体的には、下表のように"(?…)"という形式でパターンに組み込みます。

先読み/後読み 肯定 否定
先読み "パターン(?=肯定文字)" "パターン(?!否定文字)"
後読み "(?<=肯定内容)パターン" "(?<!否定内容)パターン"

まずは次のようなテキストで"「」"に囲まれた文字列を普通に抽出してみます。

text="""
新商品「ハイパードリンク」
新商品「スーパードリンク」(オススメ)
従来品「ノーマルドリンク」(廃盤)
"""

re.findall("「.+?」", text)
# ['「ハイパードリンク」', '「スーパードリンク」', '「ノーマルドリンク」']

先読み肯定で、"オススメ"が続くという条件を付与してみます。

re.findall("「.+?」(?=(オススメ))", text)
# ['「スーパードリンク」']

次に先読み否定で、"廃盤"が続かないという条件を付与してみます。

re.findall("「.+?」(?!(廃盤))", text)
# ['「ハイパードリンク」', '「スーパードリンク」']

次に後読み肯定で、"新商品"に続くという条件を付与してみます。

re.findall("(?<=新商品)「.+?」", text)
# ['「ハイパードリンク」', '「スーパードリンク」']

最後に後読み肯定で、"新商品"に続かないという条件を付与してみます。

re.findall("(?<!新商品)「.+?」", text)
# ['「ノーマルドリンク」']

先読み/後読みは、条件を付与するときに便利な機能ですね。

正規表現内にコメント記載 #

正規表現は作り込んでいくと、何が書いてあるのかよくわからなくなってきます。

そんな時は、正規表現にコメントを記入しましょう。

次のように、"re.VERBOSE"(or "re.X")フラグを設定すると、正規表現内にコメントが記載できます。

  • "re.compile(パターン, re.VERBOSE)"
  • "re.search(パターン, re.VERBOSE)"

"re.VERVOSE"を有効にすると、空白・改行が無視され、"#"から行末までがコメントとして認識されます。

p = re.compile("""\d{4}年      # 4ケタの数字で、年を探す
                  \d{1,2}月    # 1~2ケタの数字で、月を探す
                  \d{1,2}日    # 1~2ケタの数字で、日を探す
""", re.VERBOSE)

p.search("2012年12月25日")
# <re.Match object; span=(0, 11), match='2012年12月25日'>

コメントがあると、正規表現パターンの意味がわかりやすくなりますね。

正規表現の応用例

今回は正規表現のパターン作成方法について解説しました。

しかし、正規表現は使わないことには、なかなか身につきません…。

次の記事で使用例を紹介しているので、ぜひ読んで参考にしてみてください!

≫Amazonの購入履歴をスクレイピングなしで抽出
簡易的にAmazonの注文履歴を整理したい場合のPythonスクリプトです。「Amazonの注文履歴から金額と商品名と日付を抽出したいな」、「とはいえ、わざわざSeleniumを使ってスクレイピングするほどでもないな」、「でも、手作業で集計するのはめんどくさいな・・・」。こういったときに、さらっとAmazonの注文履歴から金額、商品名、日付を抽出するPythonスクリプトを紹介します。
www.yutaka-note.com/entry/ama2df
 
≫aタグのリンクを新規タブで開くようにするPythonスクリプト
htmlのaタグのリンクを外部リンクで開くようにaタグを更新するpythonスクリプトを紹介します。テキスト形式でhtmlを用意して、pythonスクリプトを実行すると自動でaタグの属性を更新してくれます。
www.yutaka-note.com/entry/a_tag_jido