[Python] 使用 UN Comtrade API 高效获取数据

article/2025/10/21 21:25:43

本文最初写成于 2021 年 7 月,由于作者的拖延问题以致今天才得以和各位同学(即便大概率是自说自话了)见面,因此文中具体细节可能已经发生变化。

Cover Photo by Maksym Kaharlytskyi on Unsplash

使用 UN Comtrade API 批量下载数据

  • 写在前面
  • 背景知识
    • 什么是 [UN Comtrade DB](https://comtrade.un.org/)
    • The UN Comtrade data extraction API
    • Python 知识
  • Hands-on
    • Understanding the Data Request Form
    • Dynamic IP
    • Coding
      • Part 1 Download specific URL to target location
      • Part 2 The Main Function
      • Part 3 Multithreading
      • Part 4 The Last few lines
  • Conclusion
  • Future Works

写在前面

谨以本文记录我与 UN Comtrade Database 的初次交手,四天时间里的爬虫初体验着实让我感到精疲力竭。今天我把这经验分享在这里,抛砖引玉,也许能给后来的同学一些启发。

背景知识

这一部分将向你介绍什么是 UN Comtrade Database,其 API 的使用方法,和所需的 Python 知识

什么是 UN Comtrade DB

UN COMTRADE is the pseudonym for United Nations International Trade Statistics Database. Over 170 reporter countries/areas provide the United Nations Statistics Division (UNSD) with their annual and monthly l international trade statistics data detailed by commodities/service categories and partner countries.

根据联合国统计司 knowledge base 的上述说明,你可以在 Comtrade Database 中查询170+国家报告的各品类商品/服务的年度/月度贸易数据。数据的具体类型将会在 Hands-on 部分详细分析,在此之前你可以通过官网的在线预览粗略感知,在这里你可以在这一页面指定时间、产品类型、产品种类、报告国和合作国等特征。
你可以在这一页面指定产品类型、产品种类、报告国、合作国等等特征

The UN Comtrade data extraction API

我们可以通过这一 API 来提取数据库中的特定数据,以 CSV 或 JSON 格式下载,或利用 AJAX call 来将数据融入进你的网页。

统计司鼓励用户把自己的可视化作品分享给他们,优秀的作品有可能被引用在 UN Comtrade Labs 中。我在这里发现了很多有意思的项目,非常有启发性(aka 我实在是太菜自己根本搞不出来)

Python 知识

只需了解基本语法即可!如果你对 Python 感到陌生,可以跟着廖雪峰的官方网站的 Python 教程学习,只需要学到高级特性部分就足够理解下面涉及的所有内容了。

Hands-on

我的工作是根据博主王蛋糕cake的这两篇慷慨的博文的完善,针对中断处理 (刚刚考完计组,写到这里不由得心头一颤) 和下载速度和下载内容有效性的判断等方面进行了优化。此外,还补充了下载后的清理过程,清理下载失败的文件。

  1. UN Comtrade(联合国商品贸易统计数据库)数据爬取Python代码
  2. UN Comtrade(联合国商品贸易统计数据库)数据爬取Python代码——使用动态IP

Understanding the Data Request Form

通过阅读 API文档 ,我们能在 UN Comtrade data request format 部分了解到发送请求的基本格式:

http://comtrade.un.org/api/get?parameters

其中,parameters 允许的参数在文档中有完整的说明,我就不再在此处赘述了,但其中值得强调的一点是:如果 freq 参数赋值为 M (代表以月份为单位获取数据)时,px (classification) 参数不要选择 SITC 那一套 (ST, S1, S2, … , S4) 因为没有这样的数据,你获得的都将是空表。

假设,我们现在想要查询 csv 格式下,美国2020年9月的AG4精度的进出口商品数据,该如何写这个请求 url 呢?

其中一种答案是:

https://comtrade.un.org/api/get?max=100000&type=C&freq=M&px=HS&ps=202006&r=all&p=842&rg=all&cc=AG4&fmt=csv

也许和你的答案有些出入,请注意API 对参数的前后顺序并不敏感;r 参数和 p 参数任意分别设置为 842 (code for USA) 和 all 即可;如果仔细的阅读过 Knowledgebase 文档,你就会清楚:对于同一条贸易数据,站在 importer 和 exporter 的视角(指选择 importer/exporter 为 reporter),trade value 是不同的,通常 import value 会高于 export value 因为:

Imports is generally reported on the basis of Cost, Insurance and Freight, (CIF) while exports is reported on a Free on Board (FOB) basis. For this reason, import values tend to be higher than export values.

另,根据 Worldbank 的提示,通常来讲,import data 更加准确,出于关税计算的原因。

但回到现实,这样的请求很难获得目标数据;因为美国的进出口贸易量较大,4 digits HS Code (AG4) 描述下的进口和出口数据条数实在是太大了,很可能超过 guest 用户的下载条数限额。
面对这样的问题,如果我们不愿意在精度上做出妥协,那么就需要将这条请求拆分为多条,这里可以有很多种逻辑,例如:

  1. 分别下载进口和出口数据(by altering the rg parameter)
  2. 遍历国家代码,每次请求指定 reporter 的国家代码 (by specifying the r parameter)

这也就引出了参数设置的迷思,由于下面介绍的,对于 guest 用户的访问频率和总量的限制的存在,成为了下载条数和下载次数之间的博弈(每次下载拆分的越细,需要下载的次数就越多、由于频率的限制,下载的次数越多,耗费的时间就越长)我们要尽可能地保持平衡。

你会在 Usage limits 部分了解到:

  1. 对于 guest 用户,每秒不能发送多于 1 条请求(rate),且每小时不能发送超过 100 条请求 。
  2. 对于 ps, rp 参数,输入的限制条件 (code) 不能超过五条,上述三个参数只能出现一次 ALL;

我将在下面的例子中展示,如何通过混合 动态IP 和 简易的多线程 技术实现效率更高的数据获取。

Dynamic IP

选择 TB 买家提供的最廉价的 (15 rmb/d 至少在 07/2021 是这样的行情) 动态 IP 服务即可。我遇到的买家很贴心的提供了一些语言的不同包的 Proxy 设置语法,但对于我们的例子,只需要了解 IP 端口 用户名 和 密码,填写到下面代码的对应部分即可。

Coding

  1. Requirement: 获取2019-2021,各国家月度 AG2 精度的进出口数据;
  2. Analysis: 由于数据条数的限制,采取以下下载策略:分月度分别下载每个国家为 partner 的进口和出口数据;
  • Part 1 Download specific URL to target location

import requests
import os
import json
from random import randint
import time
import threading
import datetime# proxy varification process, it might be different depending on your proxy provider
# please change the content quoted below accordingly
proxyHost = "your host link"
proxyPort = "port your host provided"
proxyUser = "username"
proxyPass = "password"# User agents (UA)
user_agents = ["Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; AcooBrowser; .NET CLR 1.1.4322; .NET CLR 2.0.50727)","Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0; Acoo Browser; SLCC1; .NET CLR 2.0.50727; Media Center PC 5.0; .NET CLR 3.0.04506)","Mozilla/4.0 (compatible; MSIE 7.0; AOL 9.5; AOLBuild 4337.35; Windows NT 5.1; .NET CLR 1.1.4322; .NET CLR 2.0.50727)","Mozilla/5.0 (Windows; U; MSIE 9.0; Windows NT 9.0; en-US)","Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET CLR 2.0.50727; Media Center PC 6.0)","Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET CLR 1.0.3705; .NET CLR 1.1.4322)","Mozilla/4.0 (compatible; MSIE 7.0b; Windows NT 5.2; .NET CLR 1.1.4322; .NET CLR 2.0.50727; InfoPath.2; .NET CLR 3.0.04506.30)","Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN) AppleWebKit/523.15 (KHTML, like Gecko, Safari/419.3) Arora/0.3 (Change: 287 c9dfb30)","Mozilla/5.0 (X11; U; Linux; en-US) AppleWebKit/527+ (KHTML, like Gecko, Safari/419.3) Arora/0.6","Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.2pre) Gecko/20070215 K-Ninja/2.1.1","Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9) Gecko/20080705 Firefox/3.0 Kapiko/3.0","Mozilla/5.0 (X11; Linux i686; U;) Gecko/20070322 Kazehakase/0.4.5","Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.8) Gecko Fedora/1.9.0.8-1.fc10 Kazehakase/0.5.6","Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.56 Safari/535.11","Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_3) AppleWebKit/535.20 (KHTML, like Gecko) Chrome/19.0.1036.7 Safari/535.20","Opera/9.80 (Macintosh; Intel Mac OS X 10.6.8; U; fr) Presto/2.9.168 Version/11.52",def download_url(url, path, proxy):''' This function is used to download the passed url to the target file (path) using the proxy config'''time.sleep(0.5)		# you don't really need this line for the multithreads we are about to userandom_agent = user_agents[randint(0, len(user_agents) - 1)]		# chose an user agent from the user agent list abovetunnel = randint(1, 10000)		# generate a tunnelheader = {"Proxy-Tunnel": str(tunnel),"User-Agent": random_agent}try:content = requests.get(url, timeout=100, headers=header, proxies=proxy)''' note that sometimes we only get error informations in the responses, and here are some really dumb quick fixes'''if (content.text == "<html><body><h1>502 Bad Gateway</h1>\nThe server returned an invalid or incomplete response.\n</body></html>\n" or content.text == "Too Many Requests.\n" or content.text == "{\"Message\":\"An error has occurred.\"}"):with open("./data/serverError.csv", 'a', encoding="utf-8") as log:log.write(str(datetime.datetime.now()) + "," + str(url) + "," + str(path) + "\n")print("\n" + content.content.decode())download_url(url, path, proxy)else:# write csv file;with open(path, 'wb') as outfile:outfile.write(content.content)print("download finished")time.sleep(0.5)except requests.RequestException as e:''' I have absolutely no knowledge about Request Exception Handling so I chose to write the error information to a log file'''print(type(e).__name__ + " has occurred")with open("./data/exp.csv", 'a', encoding="utf-8") as log:log.write(str(datetime.datetime.now()) + "," + str(type(e).__name__) + "," + str(url) + "," + str(path) + "\n")download_url(url, path, proxy)
]

为了养成良好的注释习惯同时锻炼自己半死不活的英文能力,代码中形成了密度较高的英文注释;欢迎英文/代码规范大师对我的注释 and Code 进行批评!

download_url 函数中唯一需要进一步阐明的,就是对于返回内容的简单筛选。通过无数次人工筛查,发现一些时候 api 会直接返回一些错误信息,而不是数据。由于一些到现在都没想明白的原因,我在下载的时候遇到这些错误信息的概率非常高,因此在这里直接引入了对 response 内容的检查。如果遇到错误信息内容,则重新下载 URL 。同时还会记录进一个名为 502 的 log 文件中,如果你没有这种需求,可以删掉相关代码,不会对功能造成损害。

但值得注意的是,这并不能确保在下载完成后,我们就能获得万无一失的数据。返回 Too Many Request.\n 这行信息的 response 不论如何都无法被正确识别。一个也许更优的的思路是检测形成的 csv file 是否有 header,但在此处我没有实现。

  • Part 2 The Main Function

在开始之前,我想说各位同学不能在本文中学习到任何实用软件工程知识,看到 Main 函数的那一刻,或者说在和我写的代码产生最初接触的那一刹那你就会知道这一点。本文的代码结构实在是,太、不、面向对象了。我的初心是在博主 王蛋糕cake 的慷慨分享的基础上快速实现,因此就保留了最开始的结构。

def main(start, end):  # start and end should be 4 digits integers; note: [start, end]proxyMeta = "http://%(user)s:%(pass)s@%(host)s:%(port)s" % {"host": proxyHost,"port": proxyPort,"user": proxyUser,"pass": proxyPass,}proxies = {"http": proxyMeta,"https": proxyMeta,}# get countries' name and code;if not os.path.exists("./reporterAreas.json"):download_url("https://comtrade.un.org/Data/cache/reporterAreas.json", "./reporterAreas.json")with open("./reporterAreas.json", "r", encoding="utf_8_sig") as file:code_sheet = json.load(file)results = code_sheet.get("results")_id = []name = []for country in results:_id.append(country.get("id"))name.append(country.get("text"))# strip the first element for it's all_id = _id[1:]name = name[1:]# create data dir under cwd(current working directory)if not os.path.exists("./data"):os.makedirs("./data")for YYYY in range(start, end + 1):# make dir for each monthif not os.path.exists("./data/" + str(YYYY)):os.makedirs("./data/" + str(YYYY))for idx in range(0, len(_id)):  # traverse through countriesurl = "http://comtrade.un.org/api/get?max=100000&type=C&freq=A&px=HS" + "&ps=" + str(YYYY) + "&r=all&p=" + str(_id[idx]) + "&rg=1&cc=TOTAL&fmt=csv"path = "./data/" + str(YYYY) + "/imports_from_" + str(name[idx]) + ".csv"if not (os.path.exists(path) and os.path.getsize(path) != 0):print("downloading from " + url + " to " + path)download_url(url, path, proxies)url = "http://comtrade.un.org/api/get?max=100000&type=C&freq=A&px=HS" + "&ps=" + str(YYYY) + "&r=all&p=" + str(_id[idx]) + "&rg=2&cc=TOTAL&fmt=csv"path = "./data/" + str(YYYY) + "/exports_to_" + str(name[idx]) + ".csv"if not (os.path.exists(path) and os.path.getsize(path) != 0):print("downloading from " + url + " to " + path)download_url(url, path, proxies)

main 需要的参数是起始年份和中止年份,注意是区间是左闭右闭的。

我是在之后的一段时间才花费时间研究获取系统传入参数的这种操作的,各位当然可以选择实现这个 feature 来更优雅的实现自动化工作。

随后会下载联合国的 Nation Code JSON 文件在 current working directory 下,如果还没有下载的话。

随后就是 URL 的组装了,调用刚刚完成的 download_url 函数并将生成的 URL 传入。

这里注意,如果已经下载过相同的数据(已经存在同名文件),则不会重复下载。了解这一点可以方便各位进行数据的清洗,在删除错误文件后,可以直接使用本脚本重新下载被删除的文件,而不会重复下载已经存在的文件而浪费更多时间

  • Part 3 Multithreading

class DownloadData(threading.Thread):def __init__(self, begin, terminate):super().__init__()self.begin = beginself.end = terminatedef run(self):main(self.begin, self.end)

新建 DowloadData 类,为了 implement thread 来实现多线程同时下载数据。但说实话,在完成本项目时,我对于多线程编程一无所知(当然除去 Java 课上锁仓库开仓库的这种奇怪却又富有禅意的课程实验了),因此很可能这样多线程会导致更加频繁的出现一些问题(存疑)。但对于我来讲,最终的结果是的确对于数据的获取速度有很好的提升。

  • Part 4 The Last few lines

print("note: [start time, end time]")
start = int(input("please specify the start year: "))
end = int(input("...and the end year: "))threads = []
for year in range(start, end + 1):thread = DownloadData(start, end)thread.start()threads.append(thread)

为每一年建立一个新的线程实例,下载所需数据。

Conclusion

本脚本能够以一定的效率完成数据的获取,但整体上还有很大的优化空间。对于进一步的错误处理(发现错误数据),我另写了另外一个脚本,判断 csv file 的内容是否为正确的数据内容,如果不是则删除。

Future Works

  1. 系统参数的传入
  2. 多线程部分的优化

http://chatgpt.dhexx.cn/article/Ab7mTPCK.shtml

相关文章

Python实现comtrade文件读取

最近在学习python&#xff0c;发现这个语言挺有意思&#xff0c;写起来比较轻松。后面打算用它代替matlab来做数据分析。写了一个comtrade格式录波文件的读取类。目前仅支持读取comtrade的binary格式文件。仅读取模拟量&#xff0c;不解析开关量。上comtrade类代码。 #!/usr/b…

Comtrade格式录波数据显示

本文介绍几种comtrade格式的电力录波查看方法&#xff0c;并给出相关的下载链接 这几个软件的具体使用方法就不一一详述了&#xff0c;最后给出使用MATLAB GUI的录波数据查看步骤 具体还是要自己动手实现 几个软件实现效果图以及相关下载链接 仅供参考 ScopeView Download 效…

COMTRADE格式录波数据分析以及函数实现(一)

一、首先&#xff0c;看一下COMTARDE是什么 COMTRADE是IEEE标准电力系统暂态数据交换通用格式。标准为电力系统或电力系统模型采集到的暂态波形和事故数据的文件定义了一种格式。该格式意欲提供一种易于说明的数据交换通用格式。IEEE于1991年提出&#xff0c;并于1999进行了修…

如何申请免费SSL证书?宝塔面板SSL证书安装部署完整教程

如何申请免费SSL证书&#xff1f;宝塔面板SSL证书安装部署完整教程 现在很多网站都由原来的http://www.xxx.com转换为https://www.xxx.com&#xff0c;这就是说从http协议转到了https协议。如果说网站不是https协议&#xff0c;Chrome浏览器会提示不安全。https比http协议安全性…

HTTPS证书及其安装

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 HTTPS证书及其安装 HTTPS证书一、 试查http协议1启动jmeter&#xff0c;添加测试元件1&#xff09;添加缓存管理器2&#xff09;添加cookie管理器3&#xff09;添加http请求默…

【Linux】宝塔面板 SSL 证书安装部署

宝塔面板 SSL 证书安装部署 前言证书下载宝塔配置SSL注意事项 前言 前期有讲过Tomcat和Nginx分别部署SSL证书&#xff0c;但也有好多小伙伴们私信我说&#xff0c;帮忙出一期宝塔面板部署SSL证书的教程&#xff0c;毕竟宝塔的用户体量也是蛮大的&#xff0c;于是宠粉的博主&am…

宝塔面板 SSL 证书安装部署

注册与购买域名-Tencent Cloud 腾讯云、华为云、阿里云等都可以购买域名并备案做dns解析。需要主要&#xff1a;域名的购买可以任意选云服务厂商&#xff0c;但是dns解析时只能指向dns服务商的主机。简单的说&#xff1a;腾讯云的dns服务器可以解析腾讯云的云服务器(ECS),不能…

javaScript中的鼠标事件

鼠标点击事件 onclick单击事件 鼠标单击时事件处理函数 <input type"button" id"bt" value"点击"> <script> //找到按钮并设置点击事件document.getElementById("bt").onclick function (){//被点击后弹出弹出框alert…

JavaScript鼠标事件与函数

通过鼠标触发事件&#xff0c;类似用户的行为&#xff1a; 鼠标事件列表&#xff0c;要在(body)里设一个div,id名要为box&#xff0c;style里设置它的的宽、高&#xff0c;然后再script里设置脚本语言&#xff0c;使他在里面能够运行。下面script都例了鼠标每一样事件&#xff…

java鼠标js触发事件吗,JavaScript鼠标事件是什么?JavaScript鼠标事件详解

js中是比较简单的语言&#xff0c;然而js的精髓就是js事件&#xff0c;这也是js当中最重要的部分&#xff0c;很多人对JavaScript鼠标事件是什么还不是很了解&#xff0c;下面我们对JavaScript鼠标事件进行详解。 一&#xff1a;在js中&#xff0c;鼠标事件有很多&#xff0c;其…

Js鼠标事件与函数

鼠标事件&#xff08;Mouse Events&#xff09; 通过鼠标触发事件, 类似用户的行为: 鼠标事件列表&#xff0c;要在body里设一个div&#xff0c;id名要为box&#xff0c;style里设置它的宽、高&#xff0c;然后再script里设置脚本语言&#xff0c;使它在里面能够运行。下面scr…

JS鼠标事件(非常详细)

这里写目录标题 一、 常用到的鼠标事件鼠标点击鼠标移动鼠标经过鼠标来源鼠标定位鼠标按键 一、 常用到的鼠标事件 在 JavaScript 中&#xff0c;鼠标事件是 Web 开发中最常用的事件类型&#xff0c;鼠标事件类型详细说明如下表所示&#xff1a; 鼠标事件类型 项目Valueclick…

JS鼠标事件实现动效

1 JS鼠标事件 click鼠标点击事件 事件对象.onclickfunction() {}mousedown / mouseup 鼠标按下/松开事件 事件对象.onmousedown function() {}mouseenter / mouseleave 鼠标移入/移出事件mouseover / mouseout 鼠标移入移出mousemove 鼠标移动事件mousewheel 滚轮滚动事件 注…

JavaScript鼠标事件

JavaScript鼠标事件 js中是比较简单的语言&#xff0c;然而js的精髓就是js事件&#xff0c;这也是js当中最重要的部分&#xff0c;很多人对JavaScript鼠标事件是什么还不是很了解&#xff0c;下面我们对JavaScript鼠标事件进行详解。 鼠标事件&#xff08;Mouse Events&#…

Linux运维之zabbix(四)onealert云告警平台

Linux运维之zabbix&#xff08;四&#xff09;onealert云告警平台 什么是云告警平台&#xff1f; 可以通过微信、邮件等快速接入各类警告信息&#xff0c;通过降噪、聚类、分派、通知、排班等功能&#xff0c;提高告警管理能力 云告警平台的部署 百度搜索oneallert&#xf…

zabbix集成onealert报警 、利用proxy分担server端压力

前言 告警将重要信息发送给运维「或者其他相关人」&#xff0c;及时发现并且处理问题。在所有开源监控软件里面&#xff0c;Zabbix 的告警方式无疑是最棒的。告警的方式各式各样&#xff0c;从 Email 告警到飞信、139/189邮箱、最后到微信甚至电话告警&#xff0c;接入存在各种…

利用zabbix部署onealert云报警平台

实验环境&#xff1a; server4 172.25.254.4 server4作为zabbix-server服务器&#xff0c;并在其上搭建zabbix-agent客户端。 server5 172.25.254.5 server5 作为zabbix-agent客户端 1. 部署好zabbix基本环境&#xff0c;让其监控sever4、s…

Zabbix(五)利用Zabbix部署onealert云告警平台

一、利用Zabbix部署onealert云告警平台 人的精力是有限的&#xff0c;当我们部署好zabbix监控&#xff0c;为监控主机添加各种监控项完毕之后&#xff0c;不可能时时刻刻去盯着我们的主机看&#xff0c;如果一个主机出现异常&#xff0c;我们该如何在第一时间获得其异常信息&a…

Zabbix监控整合OneAlert报警平台实现邮件、短信、微信、电话多方式报警

Zabbix传统告警方法 Email&#xff1a;调用 sendmail 、sendEmail 等脚本通过 SMTP 发送邮件 飞信&#xff1a;飞信已经退出历史舞台&#xff0c;不再有人使用 189/139&#xff1a;发送邮箱邮件至邮箱&#xff0c;邮箱将短信转到用户手机短信&#xff0c;存在一定的延迟 微…

详解 OneAlert 排班可以帮你做什么

排班的存在&#xff0c;实质是通过有序安排&#xff0c;降低企业/团队人力成本&#xff0c;提升工作效率。 阅读导航&#xff08;预计2min&#xff09; 1. 详解排班功能 轮班机制 工作时间 双视图展示 灵活调整 2. 利用排班如何助力运维团队 排班策略让告警更精准分派到人 …