Pythonを使ったデータ処理、Webスクレイピング、テキストマイニングなど、あらゆる場面で必須となるスキルが「文字列の抽出」です。
「メールアドレスだけをリストアップしたい」
このようなニーズは頻繁に発生します。
Pythonには、単純な位置指定による抽出から、特定の文字で分割する方法、さらには「正規表現」を使った複雑なパターンでの抽出まで、多彩な手法が用意されています。
この記事では、Pythonで文字列を抽出する主要な方法を、初心者にもわかりやすく、具体的なサンプルコードと実行結果を掲載しながら解説します。
受講生から評判の良いプログラミングスクール
スクール |
特徴 |
受講料金 |
| 大手比較サイトで4年連続人気NO.1!受講生からの評判も非常に高く、Web系のエンジニアを目指すならRUNTEQ一択。 | 657,000円 (最大約53万円の給付金が適用される) |
|
| 月単価80万円以上の現役エンジニア講師による指導!一度入会すればサポートは半永久的。 | 498,000円 |
|
| 格安で質の高いWeb制作スキルを習得したい人におすすめ!業界最安級の料金でありながら、コミュニティやサポートが充実。 | 129,800円~ |
|
| 完全無料でプログラミングが学べる貴重なスクール!最短1ヶ月で卒業可能。ゼロスク運営会社への就職もできる。 | 完全無料 |
|
| 長期間に渡って学習し、希少人材を目指す人に最適なスクール!受講料は高いものの、高収入を得られる人材を目指せる。 | 96~132万円 |
文字列抽出の基本:スライシング(位置指定)
スライシングは、文字列の「何番目のインデックスから何番目のインデックスまで」というように、位置(インデックス番号)を指定して部分文字列を取り出す最も基本的な方法です。
スライシングの基本 [start:stop:step]
Pythonのスライシングは、[開始位置:終了位置:ステップ] という書式で指定します。
インデックスは常に0から始まることに注意してください。
start: 抽出を開始するインデックス(この位置の文字は含まれます)stop: 抽出を終了するインデックス(この位置の文字は含まれません)step: 何文字おきに抽出するか(省略可、デフォルトは1)
text = "Python Programming"
# 012345678901234567 (インデックス番号)
# (P)y(t)h(o)n (P)r(o)g(r)a(m)m(i)n(g)
# インデックス7からインデックス14の手前までを抽出
# インデックス7は 'P' (8文字目)
# インデックス14は 'm' (15文字目)
# はインデックス7〜13までを意味します
# → 'Program' が抽出される
slice1 = text[7:14]
print(f"インデックス 7 から 14 (手前) まで: {slice1}")
# インデックス1からインデックス8の手前までを抽出
# インデックス1は 'y' (2文字目)
# インデックス8は 'r' (9文字目)
# [1:8] はインデックス1〜7までを意味します
slice2 = text[1:8]
print(f"インデックス 1 から 8 (手前) まで: {slice2}")
# 開始位置を省略すると「最初(インデックス0)」から
# インデックス6の手前(インデックス5)まで
slice3 = text[:6]
print(f"最初からインデックス 6 (手前) まで: {slice3}")
# 終了位置を省略すると「最後まで」
# インデックス7(8文字目)から最後まで
slice4 = text[7:]
print(f"インデックス 7 から最後まで: {slice4}")
# 2文字おき(step=2)に抽出
# インデックス0からインデックス10の手前までを、2つおきに
slice5 = text[0:10:2]
print(f"インデックス 0 から 10 (手前) まで (2ステップ): {slice5}")
実行結果
インデックス 7 から 14 (手前) まで: Program
インデックス 1 から 8 (手前) まで: ython P
最初からインデックス 6 (手前) まで: Python
インデックス 7 から最後まで: Programming
インデックス 0 から 10 (手前) まで (2ステップ): Pto r
ソースコードの解説
text = "Python Programming"で、対象となる文字列を定義します。コメントでインデックス番号(0始まり)を確認しています。text[7:14]は、インデックス7(’P’)からインデックス14の手前、つまりインデックス13(’m’)までを抽出します。text[1:8]は、インデックス1(’y’)からインデックス8の手前、つまりインデックス7(’P’)までを抽出します。text[:6]のように開始位置を省略すると、インデックス0からインデックス6の手前(インデックス5)までと見なされます。text[7:]のように終了位置を省略すると、インデックス7から文字列の最後までと見なされます。text[0:10:2]は、インデックス0から9までの範囲を、step=2(2つおき)に抽出します。
インデックス番号を使った1文字の抽出
スライシングで範囲を指定する代わりに、単一のインデックス番号を指定すると、その位置にある1文字だけをピンポイントで抽出できます。
text = "Python"
# 012345 (インデックス番号)
# インデックス0の文字(1文字目)
char0 = text[0]
print(f"インデックス 0 の文字: {char0}")
# インデックス3の文字(4文字目)
char3 = text[3]
print(f"インデックス 3 の文字: {char3}")
実行結果
インデックス 0 の文字: P
インデックス 3 の文字: h
ソースコードの解説
text[0]は、文字列textの先頭、インデックス0(1文字目)の文字 ‘P’ を示します。text[3]は、インデックス3(4文字目)の文字 ‘h’ を示します。
マイナスのインデックスを使った末尾からの抽出
インデックスにマイナスの値を指定すると、文字列の末尾から数えた位置の文字を抽出できます。
-1 が最後の文字、-2 が最後から2番目の文字を示します。
text = "Python Programming"
# ...-5-4-3-2-1 (マイナスインデックス)
# ...m m i n g
# ...-11-10 -9 -8 -7 -6 -5 -4 -3 -2 -1
# ...n P r o g r a m m i n g
# 最後の文字 (インデックス -1)
last_char = text[-1]
print(f"最後の文字 (インデックス -1): {last_char}")
# 最後から5番目の文字 (インデックス -5)
char_minus5 = text[-5]
print(f"最後から5番目の文字 (インデックス -5): {char_minus5}")
# 末尾の4文字を抽出 (インデックス -4 から最後まで)
last_4_chars = text[-4:]
print(f"末尾の4文字 (インデックス -4 以降): {last_4_chars}")
# インデックス -10 からインデックス -2 の手前まで
slice_minus = text[-10:-2]
print(f"インデックス -10 から -2 (手前) まで: {slice_minus}")
実行結果
最後の文字 (インデックス -1): g
最後から5番目の文字 (インデックス -5): m
末尾の4文字 (インデックス -4 以降): ming
インデックス -10 から -2 (手前) まで: programmi
ソースコードの解説
text[-1]は、末尾の文字 ‘g’ を抽出します。text[-4:]は、開始位置を「インデックス -4(末尾から4文字目の’m’)」とし、終了位置を省略(最後まで)することで、’ming’ を抽出しています。text[-10:-2]は、インデックス -10(’P’)から、インデックス -2(’n’)の手前(インデックス -3 の ‘i’)までを抽出します。
特定の文字・区切り文字を使った抽出
スライシングは位置が決まっている場合に有効ですが、実際には「カンマ(,)で区切られた部分」や「特定の単語の間の部分」を抽出したいケースが多いでしょう。
ここでは、文字(列)を目印にして抽出する方法を紹介します。
split() で区切り文字(カンマなど)を指定して分割・抽出
split() メソッドは、指定した区切り文字(デリミタ)で文字列を分割し、分割された各部分を要素とするリスト(配列)を返します。
data_text = "Apple,Banana,Orange,Grape"
# カンマ(,)を区切り文字として分割
items = data_text.split(',')
print(f"分割結果のリスト: {items}")
print(f"リストの1番目の要素: {items[0]}")
print(f"リストの3番目の要素: {items[2]}")
log_data = "2025-10-30 INFO 処理が完了しました"
# スペースを区切り文字として分割
log_parts = log_data.split(' ')
print(f"ログの分割結果: {log_parts}")
print(f"ログレベル: {log_parts[1]}")
実行結果
分割結果のリスト: ['Apple', 'Banana', 'Orange', 'Grape']
リストの1番目の要素: Apple
リストの3番目の要素: Orange
ログの分割結果: ['2025-10-30', 'INFO', '処理が完了しました']
ログレベル: INFO
ソースコードの解説
data_text.split(',')は、data_textという文字列を,が出現するたびに分割します。- 分割された結果(’Apple’, ‘Banana’, ‘Orange’, ‘Grape’)が、一つのリスト
itemsに格納されます。 items[0]やitems[2]のように、リストのインデックスを指定することで、分割後の一部を抽出できます。log_data.split(' ')では、半角スペースを区切り文字としてログデータを分割し、要素(日付、レベル、メッセージ)に分けています。
find() / index() で位置を特定してスライシングに応用
find() メソッド(または index() メソッド)は、文字列内で特定の文字列が「最初に出現する位置(インデックス)」を検索して返します。
find() と index() はほぼ同じ動作をしますが、見つからなかった場合の挙動が異なります。
find(): 見つからない場合は-1を返します。index(): 見つからない場合はエラー(ValueError)を発生させます。
sentence = "This is a pen. That is a book."
# "pen" という文字列の位置を探す
pos_pen = sentence.find('pen')
print(f"'pen' の開始位置: {pos_pen}") # 10
# "book" という文字列の位置を探す
pos_book = sentence.find('book')
print(f"'book' の開始位置: {pos_book}") # 26
# "cat" という文字列の位置を探す (見つからない)
pos_cat = sentence.find('cat')
print(f"'cat' の開始位置: {pos_cat}") # -1
実行結果
'pen' の開始位置: 10
'book' の開始位置: 26
'cat' の開始位置: -1
この find() をスライシングと組み合わせることで、「特定の文字列から特定の文字列まで」といった抽出が可能になります。
「”A”と”B”の間の文字列」を抽出する方法
find() を2回使って、開始位置と終了位置を特定し、その間をスライシングで抜き出します。
text = "【重要】会議の日程は[2025-11-15]に決定しました。"
# 開始の目印 '[' の位置を探す
start_marker = '['
start_index = text.find(start_marker)
print(f"'{start_marker}' の位置: {start_index}")
# 終了の目印 ']' の位置を探す
end_marker = ']'
end_index = text.find(end_marker)
print(f"'{end_marker}' の位置: {end_index}")
# 抽出処理
if start_index != -1 and end_index != -1:
# 開始位置([)の *次* の文字から、終了位置(])の *手前* までを抽出
extracted = text[start_index + 1 : end_index]
print(f"抽出結果: {extracted}")
else:
print("目印となる文字列が見つかりませんでした。")
実行結果
'[' の位置: 11
']' の位置: 22
抽出結果: 2025-11-15
ソースコードの解説
text.find('[')で[のインデックス(11)を取得します。text.find(']')で]のインデックス(22)を取得します。if start_index != -1 and end_index != -1:は、開始と終了の両方の目印が見つかった(-1でない)場合のみ処理を実行するための安全装置です。text[start_index + 1 : end_index]が抽出の核となる部分です。start_index + 1:[のインデックスが11なので、その次の文字(インデックス12)から抽出を開始します。end_index: スライシングの終了位置は「その手前まで」なので、]のインデックス(22)を指定すると、インデックス21の文字までが抽出対象となります。- 結果として、インデックス12から21までの ‘2025-11-15’ が抜き出されます。
高度な抽出を実現する「正規表現」(reモジュール)
スライシングや find() では対応が難しい、より複雑なルールの文字列を抽出したい場合に「正規表現」が活躍します。
正規表現は、「数字が3桁続いた後にハイフンがある」や「@ と . を含むメールアドレス形式」といった、文字列の「パターン(ルール)」を記述するための特殊な記法です。
Pythonでは、正規表現を扱うために標準ライブラリ re をインポートして使用します。
import re
re.findall(): パターンに一致する全てをリストで取得
re.findall() は、指定した正規表現パターンに一致する文字列を、対象テキストの「全て」から探し出し、リストとして返します。最もよく使う関数の一つです。
import re
text = "私の電話番号は 080-1234-5678 です。彼の番号は 090-9876-5432 です。"
# 正規表現パターン「数字3桁 - 数字4桁 - 数字4桁」
# \d は「任意の数字1文字」を意味します
# {n} は「直前のパターンがn回続く」ことを意味します
pattern = r"\d{3}-\d{4}-\d{4}"
phone_numbers = re.findall(pattern, text)
print(f"抽出された電話番号リスト: {phone_numbers}")
実行結果
抽出された電話番号リスト: ['080-1234-5678', '090-9876-5432']
ソースコードの解説
import reで正規表現モジュールを読み込みます。pattern = r"\d{3}-\d{4}-\d{4}"で、検索したいパターンを定義します。r"..."は「raw(生)文字列」を表し、バックスラッシュ\を特殊文字として解釈しないようPythonに指示するおまじないです。正規表現パターンには必須と考えましょう。\dは、任意の数字(0〜9)1文字にマッチします。{3}は、直前の\dが3回繰り返すことを意味します。-は、ハイフンという文字そのものにマッチします。- 結果として
\d{3}-\d{4}-\d{4}は、「数字3桁-数字4桁-数字4桁」というパターンになります。
re.findall(pattern, text)は、textの中からpatternに一致する部分を「すべて」探し出し、リスト(['080-1234-5678', '090-9876-5432'])で返します。
re.search(): パターンに一致する最初の箇所(マッチオブジェクト)を取得
re.search() は、パターンに一致する箇所をテキスト全体から検索し、最初に見つかった「1箇所」だけの情報を返します。
re.findall() と異なり、文字列のリストではなく、「マッチオブジェクト」という特殊なオブジェクトを返します。見つからなかった場合は None を返します。
import re
text = "注文ID: A-1025, 商品名: Book"
# パターン「アルファベット大文字1文字 - 数字4桁」
pattern = r"[A-Z]-\d{4}"
match_obj = re.search(pattern, text)
if match_obj:
print(f"マッチオブジェクト: {match_obj}")
# マッチした文字列本体は group() メソッドで取り出す
print(f"見つかった文字列: {match_obj.group(0)}")
else:
print("パターンに一致する箇所はありませんでした。")
実行結果
マッチオブジェクト: <re.Match object; span=(7, 13), match='A-1025'>
見つかった文字列: A-1025
ソースコードの解説
pattern = r"[A-Z]-\d{4}"を定義しています。[A-Z]は、AからZまでの任意の「大文字アルファベット1文字」にマッチします。
re.search(pattern, text)は、textを検索し、パターンに一致したA-1025という箇所に関する情報(re.Matchオブジェクト)をmatch_objに代入します。if match_obj:は、マッチした場合(match_objがNoneでない場合)に処理を実行します。- マッチした文字列そのもの(’A-1025’)は、
match_obj.group(0)またはmatch_obj.group()のようにgroup()メソッドを使って取り出します。
マッチオブジェクトと group() メソッドの使い方
re.search() や re.match() が返す「マッチオブジェクト」の真価は、パターンの一部(グループ)を個別に取り出せる点にあります。
正規表現パターン内で丸括弧 () を使うと、その部分が「キャプチャグループ」となります。
import re
text = "商品コード: ABC-999"
# ( ) を使ってグループ化する
pattern = r"([A-Z]{3})-(\d{3})"
match_obj = re.search(pattern, text)
if match_obj:
# group(0) または group() は、マッチ全体
print(f"全体のマッチ (group 0): {match_obj.group(0)}")
# group(1) は、1番目の ( ) にマッチした部分
print(f"グループ1 (商品種別): {match_obj.group(1)}")
# group(2) は、2番目の ( ) にマッチした部分
print(f"グループ2 (商品番号): {match_obj.group(2)}")
実行結果
全体のマッチ (group 0): ABC-999
グループ1 (商品種別): ABC
グループ2 (商品番号): 999
ソースコードの解説
pattern = r"([A-Z]{3})-(\d{3})"には、()が2組あります。- 1番目の
([A-Z]{3})がグループ1となります。 - 2番目の
(\d{3})がグループ2となります。 match_obj.group(0)は、パターン全体(ABC-999)にマッチした文字列を返します。match_obj.group(1)は、1番目の括弧([A-Z]{3})にマッチした ‘ABC’ を返します。match_obj.group(2)は、2番目の括弧(\d{3})にマッチした ‘999’ を返します。
re.match() と re.search() の違い
re.match() も re.search() と似ていますが、決定的な違いがあります。
re.match(pattern, text): テキストの「先頭(インデックス0)」からパターンが一致するかどうかだけを判定します。先頭が一致しないと、テキストの途中に一致箇所があってもNoneを返します。re.search(pattern, text): テキストの「全体」を検索し、最初に見つかった一致箇所を返します。
import re
text = "Name: Alice, Age: 25"
# [A-Za-z]+ は「アルファベット1文字以上」
pattern = r"[A-Za-z]+"
# match() は「先頭から」検索
m_match = re.match(pattern, text)
print(f"match() の結果: {m_match.group(0)}") # Name
# search() は「全体から」検索
m_search = re.search(pattern, text)
print(f"search() の結果: {m_search.group(0)}") # Name (たまたま先頭が一致)
text_jp = "名前: 太郎, 年齢: 30"
pattern_jp = r"\d+" # 数字1文字以上
# match() は先頭が「名前:」なので一致しない
m_match_jp = re.match(pattern_jp, text_jp)
print(f"match() (日本語): {m_match_jp}") # None
# search() は全体を検索し、途中の「30」を見つける
m_search_jp = re.search(pattern_jp, text_jp)
print(f"search() (日本語): {m_search_jp.group(0)}") # 30
実行結果
match() の結果: Name
search() の結果: Name
match() (日本語): None
search() (日本語): 30
ソースコードの解説
- 最初の例では、
textの先頭が ‘Name’ でパターン[A-Za-z]+に一致するため、match()もsearch()も同じ結果を返します。 - 二番目の
text_jpの例では、パターン\d+(数字)を探します。 re.match()は先頭の「名前:」が\d+に一致しないため、Noneを返します。re.search()はテキスト全体を検索し、途中にある「30」を見つけ出します。- 基本的には、
re.search()またはre.findall()を使う場面が圧倒的に多いでしょう。
【実践】正規表現によるパターン別・文字列抽出サンプル集
ここでは、正規表現を使って「やりたいこと」を実現する具体的なサンプルコードを紹介します。
正規表現のパターンは複雑に見えますが、コピーして使い、徐々に慣れていくのが早道です。
サンプル①: 括弧 () や [] の中身だけを抽出する
find() を使った例よりも、正規表現を使えば「括弧が複数あってもすべて抽出する」といった処理が簡単になります。
import re
text = "会議は [Zoom] で行います。 (資料は [Conf] にあります)"
# [ ] の中身を抽出
# \[ と \] は、括弧そのものを意味する (エスケープ)
# (.*?) は、括弧の内部の任意の文字列(最短マッチ)を意味する
pattern_kakko = r"\[(.*?)\]"
results = re.findall(pattern_kakko, text)
print(f"[ ] の中身: {results}")
# ( ) の中身を抽出
# \( と \) を使う
pattern_maru = r"\((.*?)\)"
results_maru = re.findall(pattern_maru, text)
print(f"( ) の中身: {results_maru}")
実行結果
[ ] の中身: ['Zoom', 'Conf']
( ) の中身: ['資料は [Conf] にあります']
ソースコードの解説
r"\[(.*?)\]":\[と\]: 正規表現で特別な意味を持つ[]を、ただの文字として扱うために\(バックスラッシュ)でエスケープしています。(と):findallで「括弧の中身だけ」を抽出対象にするために、キャプチャグループ()を使っています。.*?:.は任意の1文字、*は0回以上の繰り返し、?は「最短マッチ(貪欲でない)」を意味します。[A]B[C]があった場合、[A]B[C]ではなく[A]と[C]を個別に取得するための重要な記法です。
サンプル②: テキストから数字・数値だけをすべて抽出する
テキスト内に散らばる数字(整数、小数など)をまとめて抜き出します。
import re
text = "商品Aは 1,200円 (税込 1320円) です。在庫は 30.5kg あります。"
# \d+ は 1桁以上の数字
pattern_int = r"\d+"
# カンマ(,)を含む数字も対象にする
pattern_int_comma = r"[\d,]+"
# 整数と小数を抽出
pattern_float = r"[\d\.]+" # \d (数字) または . (ドット) が1文字以上
results_int = re.findall(pattern_int, text)
print(f"数字(\\d+): {results_int}") # 1, 200, 1320, 30, 5
results_int_comma = re.findall(pattern_int_comma, text)
print(f"カンマ含む数字: {results_int_comma}") # 1,200, 1320, 30, 5
results_float = re.findall(pattern_float, text)
print(f"小数含む数字: {results_float}") # 1,200, 1320, 30.5
実行結果
数字(\d+): ['1', '200', '1320', '30', '5']
カンマ含む数字: ['1,200', '1320', '30', '5']
小数含む数字: ['1,200', '1320', '30.5']
ソースコードの解説
\d+: 最も単純な「数字の連続」です。1,200は1と200に、30.5は30と5に分離されてしまいます。[\d,]+:[と]は文字クラスを表し、「数字またはカンマ」のいずれかの文字が1回以上続く、という意味になります。1,200は抽出できますが、30.5は30と5に分離されます。[\d\.]+: 「数字またはドット」が1回以上続く、という意味です。30.5を正しく抽出できます。- ※より厳密なパターン(例:
\d{1,3}(,\d{3})*(\.\d+)?)も可能ですが、まずは簡単なパターンから慣れるとよいでしょう。
サンプル③: テキストからメールアドレスの形式を抽出する
メールアドレスは形式が複雑なため、正規表現の学習に最適な題材です。
import re
text = "連絡先は <support@example.com> です。 個人用は (test.user_123@example.co.jp) です。"
# メールアドレスの簡易的な正規表現パターン
# [a-zA-Z0-9._-]+ : 英数字と._- が1文字以上
# @ : @マーク
# [a-zA-Z0-9.-]+ : 英数字と.- が1文字以上
# \. : ドット
# [a-zA-Z]{2,} : 英字が2文字以上
pattern = r"[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}"
emails = re.findall(pattern, text)
print(f"抽出されたメールアドレス: {emails}")
実行結果
抽出されたメールアドレス: ['support@example.com', 'test.user_123@example.co.jp']
ソースコードの解説
- このパターンは、世の中の全てのメールアドレス形式(RFC準拠)をカバーするものではありませんが、実用上ほとんどのメールアドレスを抽出できます。
[a-zA-Z0-9._-]+: ユーザー名部分。@の前。[a-zA-Z0-9.-]+: ドメイン名部分。@と.の間。\.[a-zA-Z]{2,}: トップレベルドメイン(.comや.co.jp)部分。ドットの後に2文字以上の英字。re.findallを使うことで、テキスト内の全てのメールアドレスを効率的に収集できました。
Pythonでの文字列抽出方法の使い分け
Pythonには3つの主要な抽出方法がありますが、どれを使えばよいか迷うかもしれません。
以下の基準で使い分けることをおすすめします。
① 位置が決まっている場合 → スライシング
「先頭の5文字」「末尾の3文字」のように、抽出したい部分の位置が固定されている場合は、スライシング [start:stop] が最もシンプルで高速です。
② 区切り文字が明確な場合 → split()
「カンマ(,)で区切られたデータ」「スペース( )で区切られたログ」のように、明確な区切り文字で分割したい場合は split() メソッドが最適です。
③ 複雑なパターンやルールで探す場合 → 正規表現
「数字だけ」「メールアドレス」「括弧の中身」のように、位置や区切り文字が一定でない、複雑なルール(パターン)に基づいて抽出したい場合は、正規表現(reモジュール)一択となります。
最初は難しく感じますが、使いこなせればPythonでのテキスト処理能力が格段に向上するでしょう。
まとめ
この記事では、Pythonで文字列を抽出するための主要な3つの方法(スライシング、split()、正規表現)について、具体的なサンプルコードを交えて解説しました。
なお、Pythonを体系的に学んだり、Pythonのスキルを高めたりするためには、プログラミングスクールを利用するのも有効です。
細かな疑問がすぐに解決するだけでなく、現役エンジニアが「質の高いポートフォリオ」を作成するための手助けをしてくれたり、エンジニア就職・転職のコツを教えてくれたりするなど、様々なメリットがありますので、独学に疲れた方は検討してみてはいかがでしょうか。




