无需下载即可将 xml 文件从 url 解析为 astropy votable

Parsing xml file from url to a astropy votable without downloading

http://svo2.cab.inta-csic.es/theory/fps/您可以获得天文观测中使用的许多滤波器的传输曲线。我想通过使用相应的 xml 文件(对于每个过滤器)打开 url 来获取这些数据,将其解析为有助于读取 table 的 astropy 的 votable数据轻松。

我设法通过打开文件将其转换为 UTF-8 文件并在本地保存为 xml 来做到这一点。然后打开本地文件工作正常,从下面的例子中可以明显看出。

但是我不想保存文件并再次打开它。当我尝试这样做时:votable = parse(xml_file),它引发了一个 OSError: File name too long 因为它将所有文件作为一个字符串。

from urllib.request import urlopen

fltr = 'http://svo2.cab.inta-csic.es/theory/fps/fps.php?ID=2MASS/2MASS.H'
url = urlopen(fltr).read()
xml_file = url.decode('UTF-8')
with open('tmp.xml','w') as out:
    out.write(xml_file)

votable = parse('tmp.xml')
data = votable.get_first_table().to_table(use_names_over_ids=True)

print(votable)
print(data["Wavelength"])

这种情况下的输出是:

<VOTABLE>... 1 tables ...</VOTABLE>
Wavelength
AA    
----------
12890.0
13150.0
...
18930.0
19140.0
Length = 58 rows

确实according to the API documentationvotable.parse的第一个参数要么是一个文件名,要么是一个可读的类文件对象。它没有具体说明这一点,但显然文件也必须是 seekable 这意味着它可以通过随机访问读取。

urlopen返回的HTTPResponse对象确实是一个类文件对象,带有.read()方法,所以原则上直接传给[=16=是可以的],但我就是这样发现它必须是可搜索的:

fltr = 'http://svo2.cab.inta-csic.es/theory/fps/fps.php?ID=2MASS/2MASS.H'
u = urlopen(fltr)
>>> parse(u)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "astropy/io/votable/table.py", line 135, in parse
    _debug_python_based_parser=_debug_python_based_parser) as iterator:
  File "/usr/lib/python3.6/contextlib.py", line 81, in __enter__
    return next(self.gen)
  File "astropy/utils/xml/iterparser.py", line 157, in get_xml_iterator
    with _convert_to_fd_or_read_function(source) as fd:
  File "/usr/lib/python3.6/contextlib.py", line 81, in __enter__
    return next(self.gen)
  File "astropy/utils/xml/iterparser.py", line 63, in _convert_to_fd_or_read_function
    with data.get_readable_fileobj(fd, encoding='binary') as new_fd:
  File "/usr/lib/python3.6/contextlib.py", line 81, in __enter__
    return next(self.gen)
  File "astropy/utils/data.py", line 210, in get_readable_fileobj
    fileobj.seek(0)
io.UnsupportedOperation: seek

因此您需要将数据包装在一个可搜索的类文件对象中。按照@keflavich 所写的内容,您可以使用 io.BytesIOio.StringIO 将不起作用,如下所述)。

事实证明,没有理由将 UTF-8 数据显式解码为 un​​icode。我会保留这个例子,但在我自己尝试之后,结果 parse() 适用于原始字节(我觉得有点奇怪,但没关系)。因此,您可以将 URL 的全部内容读入 io.BytesIO,它只是一个支持随机访问的内存中类文件对象:

>>> u = urlopen(fltr)
>>> s = io.BytesIO(u.read())
>>> v = parse(s)
WARNING: W42: None:2:0: W42: No XML namespace specified [astropy.io.votable.tree]
>>> v.get_first_table().to_table(use_names_over_ids=True)
<Table masked=True length=58>
Wavelength Transmission
    AA
 float32     float32
---------- ------------
   12890.0          0.0
   13150.0          0.0
       ...          ...
   18930.0          0.0
   19140.0          0.0

这通常是 Python 中对一些数据进行处理的方式,就好像它是一个文件,而不是将实际文件写入文件系统。

但是请注意,如果整个文件不能放入内存,这将不起作用。在那种情况下,您可能仍需要将其写出到磁盘。但是如果它只是为了一些临时处理并且你不想像你的例子那样乱丢你的磁盘 tmp.xml ,你总是可以使用 tempfile 模块来创建临时文件不再使用后自动删除。