Python中的不规则字符串解析
我刚开始学习python和django,现在想从我的爬虫程序中提取更多有效的信息。目前,这个爬虫会把漫画书的标题列表分成三部分,分别是出版日期、原始日期和标题,并正确地保存到一个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)
我成功地抓取到了日期,但标题就比较棘手了。在这个例子中,我希望在第二个“|”之后的信息填入三个不同的列。标题应该放到“title”这一列,它是一个字符字段;数字12(在“#”后面)应该放到“issue_num”这一列,它是一个小数字段;而括号中的内容应该放到“Special”这一字符字段里。我不太确定怎么进行这种严格的解析。
有时候,字符串中会有多个“#”(有一本漫画特别提到是一个捆绑包,“包含第#90到#95期”),还有一些字符串中有多个括号组(比如,“猩球崛起的背叛 #1 (共4期)(25本赠品封面)”)。
那么,我应该从哪个方向入手来解决这个问题呢?我对if/else语句的理解在处理更复杂的行时很快就崩溃了。我该如何高效地(如果可能的话,还要符合python的风格)解析这些行,并将它们细分,以便后续能正确地放入我的数据库中呢?
3 个回答
解析标题是比较难的部分,听起来你可以自己处理日期等其他内容。问题在于,没有一种规则可以解析所有的标题,而是有很多规则,你只能猜测哪一条适合特定的标题。
我通常通过创建一系列规则来处理这个问题,从最具体的到最一般的,然后逐个尝试,直到找到匹配的。
要编写这些规则,你可以使用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
如你所见,第一个标题解析得很正确,但第二个就不行。你需要写一堆规则来考虑你数据中每种可能的标题格式。
使用 正则表达式 模块 re
。比如,如果你有一个变量 s
,里面存的是你样本记录的第三个用 |
分隔的字段,你可以这样做:
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
错误,因为某个字段缺失。你需要调整正则表达式,直到它能正确解析你想要的所有内容。
正则表达式是解决问题的好方法。不过,如果你觉得写正则表达式不太舒服,可以试试我写的一个小工具(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
这个输出结果是一个(有序的)字典。这种方法适用于一些简单的情况,你也可以稍微调整一下,以便处理多个问题或者多个括号。
还有一点要注意:在当前版本中,你需要手动转义正则表达式中的特殊字符(比如,如果你想找到 |,你需要输入 \|)。我计划很快会对此进行改进。