如何使用 python 解析 Javascript 变量?
Posted
技术标签:
【中文标题】如何使用 python 解析 Javascript 变量?【英文标题】:How can I parse Javascript variables using python? 【发布时间】:2013-08-24 10:51:44 【问题描述】:问题:我试图从中收集数据的网站使用 javascript 来生成图表。我希望能够提取图表中正在使用的数据,但我不确定从哪里开始。例如,数据可能如下:
var line1=
[["Wed, 12 Jun 2013 01:00:00 +0000",22.4916114807,"2 sold"],
["Fri, 14 Jun 2013 01:00:00 +0000",27.4950008392,"2 sold"],
["Sun, 16 Jun 2013 01:00:00 +0000",19.5499992371,"1 sold"],
["Tue, 18 Jun 2013 01:00:00 +0000",17.25,"1 sold"],
["Sun, 23 Jun 2013 01:00:00 +0000",15.5420341492,"2 sold"],
["Thu, 27 Jun 2013 01:00:00 +0000",8.79045295715,"3 sold"],
["Fri, 28 Jun 2013 01:00:00 +0000",10,"1 sold"]];
这是定价数据(日期、价格、数量)。我在这里发现了另一个问题 - Parsing variable data out of a js tag using python - 这表明我使用 JSON 和 BeautifulSoup,但我不确定如何将其应用于这个特定问题,因为格式略有不同。事实上,在这个问题中,代码看起来更像是 python,而不是任何类型的 JSON 字典格式。
我想我可以将它作为字符串读入,然后使用 XPATH 和一些时髦的字符串编辑来转换它,但这对于已经格式化为 Javascript 变量的东西来说似乎工作量太大。
那么,在使用 python 时,我可以在这里做些什么来从这个变量中提取这种类型的有组织的数据? (我最熟悉python和BS4)
【问题讨论】:
除了=
后面的换行符和var
关键字,其余在python中都有效
那是实际的代码吗?或者它是一个名为line1
的变量,它是一个列表列表?如果是后者,你可以for list in line1: do_something_with(list[0], list[1], list2])
是一个叫line1的变量,它是加载时页面内容的一部分,是一个列表的列表。
所以基本上它是一个javascript变量的字符串?不是实际的 python 变量。恐怕您将不得不使用某种解析模块或删除所有不必要文本的字符串和exec()
。使用exec()
之后,你可以用它做各种事情。
我无法告诉解析器获取 line1 的内容吗?这似乎应该如此简单......
【参考方案1】:
如果您的格式确实只有一个或多个var foo = [JSON array or object literal];
,您可以编写一个 dotall 正则表达式来提取它们,然后将每个解析为 JSON。例如:
>>> j = '''var line1=
[["Wed, 12 Jun 2013 01:00:00 +0000",22.4916114807,"2 sold"],
["Fri, 14 Jun 2013 01:00:00 +0000",27.4950008392,"2 sold"],
["Sun, 16 Jun 2013 01:00:00 +0000",19.5499992371,"1 sold"],
["Tue, 18 Jun 2013 01:00:00 +0000",17.25,"1 sold"],
["Sun, 23 Jun 2013 01:00:00 +0000",15.5420341492,"2 sold"],
["Thu, 27 Jun 2013 01:00:00 +0000",8.79045295715,"3 sold"],
["Fri, 28 Jun 2013 01:00:00 +0000",10,"1 sold"]];\s*$'''
>>> values = re.findall(r'var.*?=\s*(.*?);', j, re.DOTALL | re.MULTILINE)
>>> for value in values:
... print(json.loads(value))
[[['Wed, 12 Jun 2013 01:00:00 +0000', 22.4916114807, '2 sold'],
['Fri, 14 Jun 2013 01:00:00 +0000', 27.4950008392, '2 sold'],
['Sun, 16 Jun 2013 01:00:00 +0000', 19.5499992371, '1 sold'],
['Tue, 18 Jun 2013 01:00:00 +0000', 17.25, '1 sold'],
['Sun, 23 Jun 2013 01:00:00 +0000', 15.5420341492, '2 sold'],
['Thu, 27 Jun 2013 01:00:00 +0000', 8.79045295715, '3 sold'],
['Fri, 28 Jun 2013 01:00:00 +0000', 10, '1 sold']]]
当然,这做了一些假设:
行尾的分号必须是实际的语句分隔符,而不是字符串的中间。这应该是安全的,因为 JS 没有 Python 风格的多行字符串。 代码实际上在每条语句的末尾都有分号,即使它们在 JS 中是可选的。大多数 JS 代码都有这些分号,但显然不能保证。 数组和对象字面量确实与 JSON 兼容。这绝对不能保证;例如,JS 可以使用单引号字符串,但 JSON 不能。但它确实适用于您的示例。 您的格式确实是这样定义的。例如,如果您的代码中间可能有var line2 = [[1]] + line1;
之类的语句,就会导致问题。
请注意,如果数据可能包含 JavaScript 文字,这些文字并非都是有效的 JSON,但都是有效的 Python 文字(这不太可能,但也不是不可能),您可以在它们上使用 ast.literal_eval
而不是json.loads
。但除非你知道是这样,否则我不会那样做。
【讨论】:
对。我认为我正在抓取的页面格式非常好,但是在某处有一个额外的 line1= 真的会搞砸。 你的意思是在某处有一个流浪的line1=
,或者你的意思是在没有var
之前有line1=
?前者将无法匹配并被跳过,这很好。后者也将无法匹配并被跳过,但可能 not 没问题。如果这是一个问题,您需要一个不同的正则表达式。真的,除非你能清楚地定义你的输入格式,否则正则表达式是不合适的;如果你只是猜测格式,你可能想要更宽松、更冗长的东西,也许是在 pyparsing
中构建的真正解析器。
这个项目的定义似乎很明确,这就是正则表达式起作用的原因。但是,我显然不知道我打算抓取的所有其他页面。我可能只需要使用正则表达式,直到出现问题并从那里开始。
只要适合在一堆页面上运行,然后在第一个失败的页面上调试正则表达式,然后重复ad nauseum,这是合理的。【参考方案2】:
好的,有几种方法可以做到这一点,但我最终只是使用正则表达式来查找 line1=
和 ;
之间的所有内容
#Read page data as a string
pageData = sock.read()
#set p as regular expression
p = re.compile('(?<=line1=)(.*)(?=;)')
#find all instances of regular expression in pageData
parsed = p.findall(pageData)
#evaluate list as python code => turn into list in python
newParsed = eval(parsed[0])
当你有良好的编码时,正则表达式很好,但是这种方法比这里的任何其他答案更好(编辑:或更糟!)?
编辑:我最终使用了以下内容:
#Read page data as a string
pageData = sock.read()
#set p as regular expression
p = re.compile('(?<=line1=)(.*)(?=;)')
#find all instances of regular expression in pageData
parsed = p.findall(pageData)
#load as JSON instead of using evaluate to prevent risky execution of unknown code
newParsed = json.loads(parsed[0])
【讨论】:
eval
不好有很多原因;使用像json.loads
或ast.literal_eval
这样的限制性更强的东西几乎总是更好。当然,即使不是文字(如 'abc' + 'def'),也可能有完全安全和有效的 Python 代码,但你至少有可能遇到表达式这意味着与 Python 不同的东西——甚至做一些不安全的事情。 (想象一下如果有人给你var line1=__import__('os').system('rm -rf ~/')
...会发生什么【参考方案3】:
下面做了一些假设,例如知道页面是如何格式化的,但是在 Python 上将示例放入内存的一种方法是这样的
# example data
data = 'foo bar foo bar foo bar foo bar\r\nfoo bar foo bar foo bar foo bar \r\nvar line1=\r\n[["Wed, 12 Jun 2013 01:00:00 +0000",22.4916114807,"2 sold"],\r\n["Fri, 14 Jun 2013 01:00:00 +0000",27.4950008392,"2 sold"],\r\n["Sun, 16 Jun 2013 01:00:00 +0000",19.5499992371,"1 sold"],\r\n["Tue, 18 Jun 2013 01:00:00 +0000",17.25,"1 sold"],\r\n["Sun, 23 Jun 2013 01:00:00 +0000",15.5420341492,"2 sold"],\r\n["Thu, 27 Jun 2013 01:00:00 +0000",8.79045295715,"3 sold"],\r\n["Fri, 28 Jun 2013 01:00:00 +0000",10,"1 sold"]];\r\nfoo bar foo bar foo bar foo bar\r\nfoo bar foo bar foo bar foo bar'
# find your variable's start and end
x = data.find('line1=') + 6
y = data.find(';', x)
# so you can get just the relevant bit
interesting = data[x:y].strip()
# most dangerous step! don't do this on unknown sources
parsed = eval(interesting)
# maybe you'd want to use JSON instead, if the data has the right syntax
from json import loads as JSON
parsed = JSON(interesting)
# now parsed is your data
【讨论】:
我绝对不会在这里使用eval
。如果要处理任何有效的 Python 文字,请使用 ast.literal_eval
。但你可能甚至不想要那个。
您的想法基本上与我的想法相同,但我只是使用了正则表达式。虽然我最终确实使用了 eval...【参考方案4】:
假设您有一个带有 javascript 行/块的 python 变量作为字符串,如"var line1 = [[a,b,c], [d,e,f]];"
,您可以使用以下几行代码。
>>> code = """var line1 = [['a','b','c'], ['d','e','f'], ['g','h','i']];"""
>>> python_readable_code = code.strip("var ;")
>>> exec(python_readable_code)
>>> print(line1)
[['a', 'b', 'c'], ['d', 'e', 'f'], ['g', 'h', 'i']]
exec()
将运行格式化为字符串的代码。在这种情况下,它会将变量 line1
设置为带有列表的列表。
你可以使用这样的东西:
for list in line1:
print(list[0], list[1], list[2])
# Or do something else with those values, like save them to a file
【讨论】:
你真的不需要去掉分号;它们是 Python 中有效的语句分隔符,即使它们只是将语句与空无分隔。但更重要的是,如果变量名可能是a_line
,strip("var ;")
将会很危险……
更重要的是,尝试将 JS 转换为 Python 并执行它真的非常非常不安全且不安全。它将在与仅在右侧拆分和调用ast.literal_eval
完全相同的情况下安全地工作,但这会更安全,并且允许您将变量存储在您想要的任何位置(例如,在字典中)强迫他们成为当地人(这几乎不是你想要的)。以上是关于如何使用 python 解析 Javascript 变量?的主要内容,如果未能解决你的问题,请参考以下文章