【问题标题】:Irregular String Parsing on PythonPython上的不规则字符串解析
【发布时间】:2011-10-08 16:06:17
【问题描述】:

我是 python/django 的新手,我正在尝试从我的爬虫中获取更有效的信息。目前,scraper 获取漫画书标题列表,并正确地将它们分成三部分(出版日期、原始日期和标题)的 CSV 列表。然后我将当前日期和标题传递到我的数据库的不同部分,我在我的加载程序脚本中执行此操作(将 mm/dd/yy 转换为 yyyy-mm-dd,保存到“pub_date”列,标题转到“title”柱子)。

一个普通的字符串可以是这样的:

10/12/11|10/12/11|Stan Lee's Traveler #12 (10 Copy Incentive Cover)

我成功抓取了日期,但标题比较棘手。在这种情况下,理想情况下,我希望用第二个“|”之后的信息填充三个不同的列。标题应该转到“标题”,一个字符域。数字 12(在 '#' 之后)应该进入 DecimalField“issue_num”,而 '()' 之间的所有内容都应该进入“Special”字符域。我不确定如何进行这种严格的解析。

有时,有多个#(特别是一个漫画被描述为一个捆绑包,“包含问题#90-#95”)并且有几个有多个“()”组(例如,“地球的背叛” Of The Apes #1 (Of 4)(25 Copy Incentive Cover) )

什么是开始解决这个问题的好方法?对于更复杂的行,我对 If/else 语句的了解很快就崩溃了。我怎样才能有效地(如果可能的话)pythonic解析这些行并将它们细分,以便以后可以将它们插入数据库中的正确位置?

【问题讨论】:

  • 你描述的不是严格的解析。您需要定义您的输出将是什么。例如,给定“包含问题 #90-#95”,issue_num 中应该包含什么内容?字符串“包含问题”是否应该出现在title 中?多个“()”组呢? ......无论如何,这种临时的“智能解析”往往会陷入疯狂。寻找其他方式来获取信息,或为令人沮丧的经历做好准备。
  • 对于“#90-#95”的一个实例,我打算给它一个空值作为问题编号,而不是给它一个卷号。对于多个组,第二组()总是包含我想要的部分,所以(理论上)我会考虑任何有两组括号的人,并要求它只查看第二组,删除(或至少不引用)其余部分。

标签: python django string parsing web


【解决方案1】:

使用regular expression 模块re。例如,如果您在变量s 中有示例记录的第三个|-delimited 字段,那么您可以这样做

match = re.match(r"^(?P<title>[^#]*) #(?P<num>[0-9]+) \((?P<special>.*)\)$", s)
title = match.groups('title')
issue = match.groups('num')
special = match.groups('special')

您将在最后三行中得到一个 IndexError 来表示缺少的字段。调整 RE,直到它解析出您想要的所有内容。

【讨论】:

    【解决方案2】:

    解析标题是困难的部分,听起来您可以自己处理日期等。问题是没有 one 规则可以解析每个标题,但是有 许多 规则,您只能猜测哪个规则适用于特定标题。

    我通常通过创建一个规则列表来处理这个问题,从最具体到一般,并逐个尝试直到匹配。

    要编写此类规则,您可以使用re 模块甚至pyparsing

    总体思路是这样的:

    class CantParse(Exception): 
        pass
    
    # one rule to parse one kind of title
    import re
    def title_with_special( title ):
        """ accepts only a title of the form
         <text> #<issue> (<special>) """
        m = re.match(r"[^#]*#(\d+) \(([^)]+)\)", title)
        if m:
            return m.group(1), m.group(2)
        else:
            raise CantParse(title)
    
    
    def parse_extra(title, rules):
        """ tries to parse extra information from a title using the rules """
        for rule in rules:
            try:
                return rule(title)
            except CantParse:
                pass
    
        # nothing matched
        raise CantParse(title)
    
    
    # lets try this out
    rules = [title_with_special] # list of rules to apply, add more functions here
    titles = ["Stan Lee's Traveler #12 (10 Copy Incentive Cover)",
              "Betrayal Of The Planet Of The Apes #1 (Of 4)(25 Copy Incentive Cover) )"]
    
    for title in titles:
        try:
            issue, special = parse_extra(title, rules)
            print "Parsed", title, "to issue=%s special='%s'" % (issue, special)
        except CantParse:
            print "No matching rule for", title
    

    如您所见,第一个标题被正确解析,但不是第二个。您必须编写一堆规则来说明数据中每种可能的标题格式。

    【讨论】:

    • 谢谢!这让我可以识别问题编号/特殊情况,但是我该如何继续在循环中记录它们? book.issue_num = ??
    • @Alxjrvs:解析所有标题格式还有很多工作要做。规则函数只返回 (issue_num, special) 对,所以你可以使用它。
    • 谢谢。我有点迷茫 - 我将如何在脚本中调用它?我不确定是什么持有问题编号和特殊的值。
    • 别在意我的最后一条评论——我的一个转录错误让我一度变得愚蠢。效果很好!
    【解决方案3】:

    正则表达式是要走的路。但是如果你写得不舒服,你可以试试我写的一个小解析器(https://github.com/hgrecco/stringparser)。它将字符串格式 (PEP 3101) 转换为正则表达式。在您的情况下,您将执行以下操作:

    >>> from stringparser import Parser
    >>> p = Parser(r"{date:s}\|{date2:s}\|{title:s}#{issue:d} \({special:s}\)")
    >>> x = p("10/12/11|10/12/11|Stan Lee's Traveler #12 (10 Copy Incentive Cover)")
    OrderedDict([('date', '10/12/11'), ('date2', '10/12/11'), ('title', "Stan Lee's Traveler "), ('issue', 12), ('special', '10 Copy Incentive Cover')])
    >>> x.issue
    12
    

    这种情况下的输出是一个(有序的)字典。这适用于任何简单的情况,您可以对其进行调整以捕获多个问题或多个 ()

    还有一件事:请注意,在当前版本中,您需要手动转义正则表达式字符(即,如果要查找 |,则需要键入 \|)。我打算尽快更改此设置。

    【讨论】:

      猜你喜欢
      • 2021-01-24
      • 1970-01-01
      • 2011-11-22
      • 1970-01-01
      • 2021-10-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-04-03
      相关资源
      最近更新 更多