在字符串中匹配最后一个年份的正则表达式

3

我已经编写了一个Python脚本,其中包含以下函数,该函数以包含多个日期的文件名作为输入。

代码

import re
from datetime import datetime

def ExtractReleaseYear(title):
    rg = re.compile('.*?([\[\(]?((?:19[0-9]|20[01])[0-9])[\]\)]?)', re.IGNORECASE|re.DOTALL)
    match = rg.search(title) # Using non-greedy match on filler
    if match:
        releaseYear = match.group(1)
        try:
            if int(releaseYear) >= 1900 and int(releaseYear) <= int(datetime.now().year) and int(releaseYear) <= 2099: # Film between 1900-2099
                return releaseYear
        except ValueError:
            print("ERROR: The film year in the file name could not be converted to an integer for comparison.")
            return ""

print(ExtractReleaseYear('2012.(2009).3D.1080p.BRRip.SBS.x264'))
print(ExtractReleaseYear('Into.The.Storm.2012.1080p.WEB-DL.AAC2.0.H264'))
print(ExtractReleaseYear('2001.A.Space.Odyssey.1968.1080p.WEB-DL.AAC2.0.H264'))

输出

返回结果:2012 -- 我希望这个结果是2009(即字符串中最后一次出现的年份)

返回结果:2012 -- 这个结果是正确的!(最后一次出现的年份是第一个,因此正确)

返回结果:2001 -- 我希望这个结果是1968(即字符串中最后一次出现的年份)

问题

可以看到,正则表达式只会匹配年份的第一次出现,而不是最后一次。这是有问题的,因为有些标题(比如这里包含的标题)以年份开头。

我已经搜索了获取最后一次出现的年份的方法,比如负向先行断言重复组的最后一次出现URL中的最后4位数字,但这些方法都没有让我更接近实现所需的结果。目前还没有现成的问题回答这个独特的情况。

预期结果

  • 我想要从给定的文件名中提取年份的最后一次出现,并使用上述输出引用中的现有定义/函数返回它。虽然我已经使用了在线正则表达式参考资料,但我对正则表达式还很陌生,希望有人能向我展示如何实现这个过滤器来处理上面的文件名。谢谢大家。

由于您总是检查group(1),它会给您第一个匹配项。检查组长度,如果超过一个,则取最后一组的匹配值。 - Supriya
这个问题没有真正的解决方案,特别是因为文件名可以有很多形式,例如,你的文件名中并不一定都有“1080”后面跟着一个“p”的情况。 - Casimir et Hippolyte
...或者1888年的路易·勒普林斯电影。 - Casimir et Hippolyte
我已将下限扩展到 1888。 假设标题“2018.3D.1080p.BRRip.SBS.x264.AAC”通过函数运行,它返回 2018(注意电影标题包含年份,文件名中缺少发行年)。 在这种情况下,最后一次出现也是第一次出现。 如果最后一次出现是某种上传/记录日期,例如“2018.3D.1080p.BRRip.SBS.x264.AAC.01-01-2018”,那么确实会引起关注,我希望找到智能过滤此类情况的方法。 现在,我确信这是电影标题中罕见的格式。 - ProGrammer
问题在于你可能会遇到不同种类的“异常”:New.York.1997.mvk(<=1981),Los Angeles 2013.avi(<=1996),1984.avi(<=2003),Les valseuses.1974.1920[a non word character]1080.mpg,以及一个带有第二个版本(重新制作或添加额外场景)的电影,其中包括原始发行年份和第二个版本的发行年份。 - Casimir et Hippolyte
显示剩余2条评论
3个回答

2
根据 @kenyanke 的回答,选择 findall() 而不是 search() 将是更好的选择,因为前者返回所有非重叠匹配模式。 您可以选择最后一个匹配模式作为 releaseYear。 这是我的正则表达式来查找 releaseYear
rg = re.compile(r'[^a-z](\d{4})[^a-z]', re.IGNORECASE)
match = rg.findall(title)
if match:
        releaseYear = match[-1]

上面的正则表达式假设releaseYear前后紧跟着的字母是非字母字符。三个字符串的结果(match)如下:
['2009']
['2012']
['1968']

1

你需要更改两个地方:

  1. 第一个懒惰模式.*?必须变成贪婪模式.*(在这种情况下,.*后面的子模式将匹配字符串中的最后一次出现)
  2. 你需要使用的组是第2组,而不是第1组(因为它是存储年份数据的组)。或者使第一个捕获组不捕获。

请参见this demo:

rg = re.compile('.*([\[\(]?((?:19[0-9]|20[01])[0-9])[\]\)]?)', re.IGNORECASE|re.DOTALL)
...
releaseYear = match.group(2)

或者:

rg = re.compile('.*(?:[\[\(]?((?:19[0-9]|20[01])[0-9])[\]\)]?)', re.IGNORECASE|re.DOTALL)
...
releaseYear = match.group(1)

1
考虑使用findall()而不是search()?
它会将从左到右找到的所有值放入一个列表中,只需访问最右边的值即可获得所需内容。
import re
from datetime import datetime

def ExtractReleaseYear(title):
    rg = re.compile('.*?([\[\(]?((?:19[0-9]|20[01])[0-9])[\]\)]?)', re.IGNORECASE|re.DOTALL)
    match = rg.findall(title)

    if match:
        try:
            releaseYear = match[-1][-1]
            if int(releaseYear) >= 1900 and int(releaseYear) <= int(datetime.now().year) and int(releaseYear) <= 2099: # Film between 1900-2099
                return releaseYear
        except ValueError:
            print("ERROR: The film year in the file name could not be converted to an integer for comparison.")
            return ""

print(ExtractReleaseYear('2012.(2009).3D.1080p.BRRip.SBS.x264'))
print(ExtractReleaseYear('Into.The.Storm.2012.1080p.WEB-DL.AAC2.0.H264'))
print(ExtractReleaseYear('2001.A.Space.Odyssey.1968.1080p.WEB-DL.AAC2.0.H264'))

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