Python正则表达式:排除某些结果

3
我是新手,正在尝试提取歌词中的段落标题、伴唱和主唱部分。以下是一些歌词的示例:
[Intro]
D.A. got that dope!

[Chorus: Travis Scott]
Ice water, turned Atlantic (Freeze)
Nightcrawlin' in the Phantom (Skrrt, Skrrt)...

诗歌标题包括方括号及其之间的任何单词。它们可以通过使用html标签中的"```

```"成功地隔离出来。
r'\[{1}.*?\]{1}'

和诗歌标题类似,合唱部分的歌词被放置在括号内。使用以下方法可以成功地将其隔离出来:
r'\({1}.*?\){1}'

对于主唱部分,我使用了

r'\S+'

该方法可以隔离主音轨,但也会同时隔离副歌标题和伴唱部分。我无法通过简单的正则表达式仅隔离主音轨。

这是一个Python脚本,可以得到我想要的输出结果,但我想通过正则表达式来完成(作为学习练习),但我不能从文档中找到解决方法。

import re

file = 'D:/lyrics.txt'
with open(file, 'r') as f:
    lyrics = f.read()

def find_spans(pattern, string):
    pattern = re.compile(pattern)
    return [match.span() for match in pattern.finditer(string)]

verses = find_spans(r'\[{1}.*?\]{1}', lyrics)
backing_vocals = find_spans(r'\({1}.*?\){1}', lyrics)
main_vocals = find_spans(r'\S+', lyrics)

exclude = verses
exclude.extend(backing_vocals)

not_main_vocals = []
for span in exclude:
    start, stop = span
    not_main_vocals.extend(list(range(start, stop)))

main_vocals_temp = []
for span in main_vocals:
    append = True
    start, stop = span
    for i in range(start, stop):
        if i in not_main_vocals: 
            append = False
            continue
    if append == True: 
        main_vocals_temp.append(span)
main_vocals = main_vocals_temp

1
请注意,在 x{1} 中,{1} 是无用的。x 已经寻找一个且仅一个字符。 - Kaddath
另外,一般来说,很难找到一个正则表达式可以轻松地一次性提取出你的“主唱”。它的工作方式是,如果你已经用正则表达式提取了“诗歌”和“伴奏”,那么只需使用这些正则表达式在“主唱”中替换为空字符串即可。回到我之前的评论,这意味着所有的“{1}”都可以被删除。 - Kaddath
2个回答

1
尝试这个 演示
pattern = r'(?P<Verse>\[[^\]]+])|(?P<Backing>\([^\)]+\))|(?P<Lyrics>[^\[\(]+)'

你可以使用 re.finditer 来分离组。
breakdown = {k: [] for k in ('Verse', 'Backing', 'Lyrics')}
for p in pattern.finditer(song):
    for key, item in p.groupdict().items():
        if item: breakdown[key].append(item)

结果:

{
  'Verse': 
    [
      '[Intro]', 
      '[Chorus: Travis Scott]'
    ], 
  'Backing': 
    [
      '(Freeze)', 
      '(Skrrt, Skrrt)'
    ], 
  'Lyrics': 
    [
      '\nD.A. got that dope!\n\n', 
      '\nIce water, turned Atlantic ', 
      "\nNightcrawlin' in the Phantom ", 
      '...'
    ]
}

进一步阐述这个模式,它使用命名组来分离三个不同的组。使用[^\]+]和类似的语法只是表示查找不包含](同样当\)表示不包含))。在歌词部分,我们排除任何以[(开头的内容。如果需要详细了解组件,请查看regex101演示链接
如果您不关心主歌词中的换行符,请使用(?P<Lyrics>[^\[\(\n]+)(排除\n)来将歌词转换为没有换行符的形式:
'Lyrics': [
  'D.A. got that dope!', 
  'Ice water, turned Atlantic ',
  "Nightcrawlin' in the Phantom ", 
  '...'
]

Python非常惊人,总有一天我应该学习它。虽然有人警告过我 :P - Kaddath
Python确实非常方便。虽然在这种情况下,一半的荣耀来自于正则表达式引擎本身。不确定你所说的“警告”是什么意思,我对Python只有爱而无害。 :) - r.ook
1
意思是我被告知很多次它很棒 :) 而且正则表达式也很棒,一旦你超越了那些难以理解的方面。 - Kaddath

1
你可以使用正则表达式组来查找封闭括号和开放括号之间的文本。如果你的正则表达式只有一个组(圆括号内的子模式),那么re.findall将只返回这些括号内的内容。
例如,"\[(.*?)\]"将仅查找节标题,不包括方括号(因为它们在组外)。
正则表达式"\)(.*?)\("将仅查找最后一行("\nNightcrawlin' in the Phantom ")。同样,我们可以使用"\](.*?)\["来查找第一行。
将两种类型的括号合并成字符类,更混乱的正则表达式"[\]\)](.*?)[\[\(]"将捕获所有歌词。
它会错过没有括号的行(即,如果有任何内容,则在 [Intro] 之前的开头,或在没有后面的伴奏声音时在结尾)。一种可能的解决方法是在末尾添加“]”字符和“[”字符,以强制匹配从字符串的末尾开始/结束。请注意,我们需要添加 DOTALL 选项以确保通配符“.”将匹配换行符“\n”。
import re

lyrics = """[Intro]
D.A. got that dope!

[Chorus: Travis Scott]
Ice water, turned Atlantic (Freeze)
Nightcrawlin' in the Phantom (Skrrt, Skrrt)..."""


matches = re.findall(r"[\]\)](.*?)[\[\(]", "]" + lyrics + "[", re.DOTALL)
main_vocals = '\n'.join(matches)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接