耗时一周,我统计出来了npm下载量前30的仓库,第一竟是它!
作为一个前端开发人员,我们每天都在使用npm,但是你曾经是否和我一样好奇,下载量最大的包是哪个?每天下载多少次?他们的github star是多少?上周我偶然看到了一个库 glob, 每周竟然下载8000万次,与此同时,react只有1500万次,glob是最高的吗,第一又是谁呢?
统计结果展示
耗时一周,我统计出来了npm下载量前30的仓库,第一竟是它!supports-color!总下载量 26,108,633,482 次,但 github star 竟然只有 319 个。另外,我做了一个网站,统计了最近一周、最近一月、最近一年、总下载量等各个维度的图表,还没有做优化加载可能有点慢,网站地址 https://www.npmrank.net/
无图无真相,下面是网站截图
分析npm官网接口,获取某个包的下载量
打开浏览器控制台分析npm接口发现,同一个地址,比如 https://www.npmjs.com/package/lodash , 从npm首页 Popular libraries 中推荐的库点进去,接口返回的是JSON格式的数据,而从地址栏输入链接进去,返回是服务端渲染好的html。多次控制变量法未能定位是哪个header的原因,我就先不管了(当然不是睡大觉)
- 找到返回JSON的接口,copy -> copy as fetch
- 粘贴到console
- 复制header到postman,同时看到有下载量数据
- 打开postman右侧的代码块,找到python代码
- 复制到test.py,去掉某些空的header
OK,这样获取某一个仓库的接口就完成了,通过这个接口我们可以拿到github地址,仓库版本,最近一年每周的下载量等
根据npm官方api,获取不同时间段的下载量
上面官网的接口只是最近一年各周的下载量,有没有其他时间段的呢,找了一圈后发现npm官网提供了这样的接口,官方api文档 ,
通过上面提供的接口,我们可以获取上周、上月、任何一个时间段的下载量,但是需要注意的是,官方api每次最多返回18个月的数据,最早是2015-01-10号的数据,所以统计总下载量时要分段获取每年的下载量后再累加,如果你想统计自己的包被安装了多少次,也是可以滴,接下来就是获取很多包名,循环下载后统计了
获取19年的排行
我在网上搜了一下npm download rank,发现只有 anvaka 19年做的统计符合想要的结果,他下载了npm全部的包并做了各种维度的分析,这个md是他统计的 top 1000依赖的包,不过被依赖的越多下载量越大,误差应该不会很大
保存文件到本地 SOURCE_FILE
获取包名和仓库地址并存到sqlite数据库
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19'''
从md中拿到库名并存到数据库
'''
with open(SOURCE_FILE, 'r') as f:
lines = f.readlines()
for line in lines:
name = re.findall(r'\[(.*?)\]', line)
href = re.findall(r'\((.*?)\)', line)
print('line\n', line)
if name and href:
get_pkgbase_query = '''SELECT * FROM pkgbase WHERE id = ?'''
record_base = sql_obj.get(get_pkgbase_query, (name[0],), one=True)
if record_base is None:
insert_data_query = '''
INSERT INTO pkgbase
('id', 'npm_url', 'github_url', 'homepage_url', 'version', 'license', 'github_star', 'size', created, updated)
VALUES(?,?,?,?,?,?,?,?,?,?)
'''
sql_obj.update(insert_data_query, (name[0], NPM_BASE_URL + name[0], '', '', '', '', 0, '', 0, 0))循环请求存储基本数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34'''
更新下载量
'''
async def main():
all_data_query = '''SELECT * FROM pkgbase'''
records = sql_obj.get(all_data_query)
for index, record in enumerate(records):
while True:
print('id', record['id'], index)
try:
'''
获取下载量并写入数据库
'''
href = NPM_BASE_URL + record['id']
npm_response = requests.request("GET", href, headers=npm_headers)
npm_data = npm_response.json()
# pkgbase
github_url = npm_data['packageVersion'].get('repository', '')
homepage_url = npm_data['packageVersion'].get('homepage', '')
version = npm_data['packument'].get('version', '')
license = npm_data['packument'].get('license', '')
# 有仓库两个license
license = license if type(license) == str else '-'
versions = npm_data['packument'].get('versions') if npm_data['packument'].get('versions') else []
updated = datetime.datetime.fromtimestamp(versions[0]['date']['ts'] / 1000).strftime("%Y-%m-%d %H:%M:%S")
created = datetime.datetime.fromtimestamp(versions[len(versions) - 1]['date']['ts'] / 1000).strftime("%Y-%m-%d %H:%M:%S")
update_pkgbase_query = '''
UPDATE pkgbase
SET github_url = ?, homepage_url = ?, version = ?, license = ?, updated = ?, created = ?
WHERE id = ?
'''
sql_obj.update(update_pkgbase_query, (github_url, homepage_url, version, license, updated, created, record['id']))更新各个时间段的下载量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51'''
获取某一时期的下载量
'''
def get_point_downloads(date_range, package_name):
href = f'{NPM_BASE_API_POINT_URL}{date_range}/{package_name}'
response = requests.request("GET", href)
data = response.json()
return data['downloads']
'''
获取全部下载量,npm每次最多返回18个月的数据,所以分段下载后再累加
'''
def get_point_all_downloads(package_name):
start_time = 2015
end_time = datetime.datetime.now().year
all_downloads = 0
for year in range(start_time, end_time + 1):
dltype = f'{year}'
date_range = f'{year}-01-01:{year + 1}-01-01'
print('date_range', date_range)
downloads = get_point_downloads(date_range, package_name)
all_downloads += downloads
print('new downloads',downloads)
add_data_query = '''
INSERT INTO pkgdownload
('id', 'dltype', 'downloads', 'timepoint')
VALUES(?,?,?,?)
'''
sql_obj.update(add_data_query, (package_name, dltype, downloads, datetime.datetime.now()))
return all_downloads
...
# pkgdownload
base_dltype = ['last-day', 'last-week', 'last-month', 'last-year', 'all-time']
for dltype in base_dltype:
if dltype == 'all-time':
downloads = get_point_all_downloads(record['id'])
else:
downloads = get_point_downloads(dltype, record['id'])
print('dltype', dltype)
print('downloads', downloads)
replaced_dltype = re.sub(r'\-', '_', dltype)
add_pkgdownload_query = '''
INSERT INTO pkgdownload
('id', 'dltype', 'downloads', 'timepoint')
VALUES(?,?,?,?)
'''
sql_obj.update(add_pkgdownload_query, (record['id'], replaced_dltype, downloads, datetime.datetime.now()))
获取包的github数据
本来官网接口中返回的有ghapi字段,如 https://api.github.com/repos/lodash/lodash ,里面有stargazers_count字段就是star数,但是该接口每小时限速60次,所以无奈只能用爬虫了,代码如下
1 |
|
第一次使用爬虫库 bs4 的 BeautifulSoup 模块,获取 github star 只有两行代码,也太方便了吧
就在刚才发现npm也有接口会返回github star数,如 https://api.github.com/repos/lodash/lodash/pulls?per_page=1 里的 stargazers_count ,等有时间我替换一下
开启服务
经过上面一通操作,我们现在有了pkgbase、pkgdownload 这样两张表,内容如下
接下来写两个接口,一个是返回下载量排名的的类型,过去一周,过去一年,总下载量等,供前端筛选,使用quart简单起个服务
1 |
|
根据排名类型,返回对应的排行数据
1 |
|
彩蛋
如果你看了上面开启服务的的代码,你可能会发现获取排行数据的接口其实还有一个top参数,最大是200条,但是由于图表不方便展示这么多的数据,如果你想自己看一下前200都有哪些包,可以复制接口改一下,如 https://www.npmrank.net/api/ranking/packages/last-day?top=200 ,如果你想查看超过200的排行,可以打开database.db的pkgdownload表查看
结束
以上就是获取npm排行的整个流程了,如果感觉有意思的话欢迎点个赞或者star,后端仓库地址 npmrank ,在线体验网页链接 https://www.npmrank.net/