
1. 从投稿数据里挖出会议的真实偏好ICLR 这几年的投稿量涨得有点离谱。2023 年还不到五千篇2024 年直接冲到七千多2025 年据说又创新高。我连续三年盯着 OpenReview 上的公开数据做了一些统计本来只是想看看自己投的领域竞争有多激烈结果越挖越有意思——审稿人到底偏爱什么样的关键词、哪些方向的投稿在暴涨、被拒的稿子有没有什么共同特征这些问题都能从数据里找到一些线索。这篇文章就是把这套分析流程完整拆一遍。核心工作分三块从 OpenReview 抓取投稿记录、把杂乱的 JSON 数据结构化、用词云图和统计图表把结论可视化。适合有一定 Python 基础、想自己做会议数据分析的研究生或者工程师参考。整套流程不需要什么高深技术关键是知道数据长什么样、哪些字段有用、怎么把 JSON 里嵌套的信息拍平。我用的工具很朴素Python 的 requests 拿数据pandas 做清洗jieba 和 wordcloud 出图最后用 pyecharts 做交互式词云。没有用任何付费服务全部本地跑。下面按实际操作的顺序来讲中间踩过的坑和绕过的弯路都会标出来。2. 数据获取OpenReview 的 API 到底怎么用2.1 先搞清楚 OpenReview 的数据结构OpenReview 是 ICLR 的投稿和审稿平台所有投稿的标题、摘要、关键词、审稿意见、评分都是公开的至少大部分是。它的数据通过一个 REST API 暴露出来地址格式大概是https://api2.openreview.net/notes支持按 forum、invitation、content 等字段过滤。我第一次直接拿浏览器打开 API 地址返回的是一大坨 JSON嵌套层级很深。一条典型的投稿记录大概长这样{ id: abc123, content: { title: {value: Some Paper Title}, abstract: {value: ...}, keywords: {value: [deep learning, optimization]}, venue: {value: ICLR 2024 Poster} }, invitations: [ICLR.cc/2024/Conference/-/Submission], cdate: 1690000000000 }注意content里面每个字段又包了一层value这是 OpenReview API v2 的格式。如果你按 v1 的写法直接取content.title拿到的会是一个字典而不是字符串后面处理起来全是坑。2.2 分页拉取和限流处理OpenReview 的 API 默认每页返回 1000 条用offset和limit控制。我一开始想一次性拉完结果请求超时了。后来改成每页 500 条、循环拉取中间加time.sleep(0.5)避免触发限流。import requests import time import json def fetch_notes(invitation, max_notes10000): base_url https://api2.openreview.net/notes notes [] offset 0 limit 500 while offset max_notes: params { invitation: invitation, offset: offset, limit: limit } resp requests.get(base_url, paramsparams, timeout30) if resp.status_code ! 200: print(f请求失败状态码 {resp.status_code}offset{offset}) break data resp.json() batch data.get(notes, []) if not batch: break notes.extend(batch) offset limit time.sleep(0.5) return notes这里有个细节invitation参数要写对。ICLR 2024 的投稿 invitation 是ICLR.cc/2024/Conference/-/Submission2023 年是ICLR.cc/2023/Conference/-/Submission。写错了返回空列表不报错很容易以为没数据。提示拉取之前先用浏览器或者 curl 试一条确认 invitation 和返回结构再写循环。我在这上面浪费了半小时。2.3 把原始 JSON 存成本地文件拉下来的数据不要直接处理先原样存成 JSON 文件。原因有两个一是 API 可能变二是后面调试不用反复请求。我按年份存成iclr_2023.json、iclr_2024.json、iclr_2025.json。with open(iclr_2024.json, w, encodingutf-8) as f: json.dump(notes, f, ensure_asciiFalse, indent2)ensure_asciiFalse很重要不然中文关键词会被转成\uXXXX形式后面做词云的时候还得再解码一次。3. JSON 拍平把嵌套结构变成表格3.1 为什么必须做 JSON 转换原始 JSON 是嵌套的content下面每个字段都有value包裹keywords本身还是个数组。直接拿来做统计pandas 读进去会变成 object 类型groupby 和 value_counts 全用不了。所以第一步是把每条记录拍平成一行字段包括id、title、abstract、keywords用分号拼接、venue、cdate、year。3.2 写一个健壮的拍平函数拍平的时候最怕字段缺失。有些投稿没有 keywords有些 venue 是空的直接取[value]会抛 KeyError。我写了一个带默认值的取值函数def safe_get(content, key, default): if key not in content: return default val content[key] if isinstance(val, dict) and value in val: return val[value] return val def flatten_note(note, year): content note.get(content, {}) keywords safe_get(content, keywords, []) if isinstance(keywords, list): keywords ;.join(keywords) return { id: note.get(id, ), title: safe_get(content, title), abstract: safe_get(content, abstract), keywords: keywords, venue: safe_get(content, venue), cdate: note.get(cdate, 0), year: year }跑完三年数据2023 年大概四千多条2024 年七千多2025 年更多。拍平之后存成 CSV后面所有分析都基于这个表。3.3 数据清洗的几个关键点拍平之后不能直接用还有几处要处理venue 字段混乱有的写 Poster有的写 Oral有的写 Submitted to ICLR 2024还有的是空字符串。我统一用正则提取只保留 Poster、Oral、Spotlight、Reject 这几类。keywords 分隔符不统一有的用逗号有的用分号有的用竖线。统一替换成英文分号再 split。重复记录同一篇稿子可能因为 revision 出现多条按 id 去重。时间戳转换cdate是毫秒时间戳转成日期方便看投稿时间分布。import pandas as pd df pd.read_csv(iclr_all.csv) df[keywords] df[keywords].str.replace(r[,|], ;, regexTrue) df df.drop_duplicates(subsetid) df[submit_date] pd.to_datetime(df[cdate], unitms)注意pd.to_datetime处理毫秒时间戳要加unitms不加的话会得到 1970 年的日期我第一次就踩了这个坑。4. 关键词分析词云图背后的统计逻辑4.1 关键词拆解和词频统计词云图好看但真正有价值的是词频统计。我先把 keywords 字段按分号拆开展开成一行一个关键词然后做 value_counts。from collections import Counter all_keywords [] for kws in df[keywords].dropna(): for kw in kws.split(;): kw kw.strip().lower() if kw: all_keywords.append(kw) counter Counter(all_keywords) top_50 counter.most_common(50)跑完 2024 年的数据排前面的关键词大概是deep learning、reinforcement learning、optimization、graph neural network、transformer、diffusion model、large language model、self-supervised learning。2025 年 large language model 和 diffusion 的排名明显上升reinforcement learning 稍微降了一点。这个趋势和实际投稿感受是一致的。4.2 用 wordcloud 生成基础词云Python 的 wordcloud 库是最省事的方案。但直接拿英文关键词做词云有个问题词形不统一比如 graph neural networks 和 graph neural network 会被当成两个词。我加了一个简单的词形还原把复数 s 去掉粗糙但够用。from wordcloud import WordCloud import matplotlib.pyplot as plt freq_dict dict(counter.most_common(200)) wc WordCloud( width1600, height900, background_colorwhite, max_words150, colormapviridis ) wc.generate_from_frequencies(freq_dict) plt.figure(figsize(16, 9)) plt.imshow(wc, interpolationbilinear) plt.axis(off) plt.savefig(iclr_keywords_wordcloud.png, dpi200, bbox_inchestight)生成的图能看但有个明显问题词云只能看大概具体哪个词排第几看不出来。所以我又补了一个横向柱状图取 top 30 关键词画条形图比词云精确得多。4.3 用 pyecharts 做交互式词云静态词云发在博客里还行但如果是做内部汇报交互式的更直观。pyecharts 的 WordCloud 组件支持鼠标悬停显示词频还能导出 HTML。from pyecharts import options as opts from pyecharts.charts import WordCloud data [(word, freq) for word, freq in counter.most_common(150)] wc ( WordCloud() .add(, data, word_size_range[12, 80], shapecircle) .set_global_opts( title_optsopts.TitleOpts(titleICLR 2024 投稿关键词词云), tooltip_optsopts.TooltipOpts(is_showTrue) ) ) wc.render(iclr_wordcloud.html)pyecharts 默认的字体对中文支持不好如果关键词里有中文要手动指定字体路径。英文关键词没这个问题。提示pyecharts 生成的 HTML 文件可以直接用浏览器打开不需要起服务器。如果要嵌入网页用 iframe 引进去就行。4.4 关键词共现分析单看词频只能知道哪些方向热看不出方向之间的关系。我顺手做了一个共现矩阵同一篇投稿里出现的两个关键词算一次共现统计 top 30 关键词之间的共现次数用热力图展示。import itertools import numpy as np import seaborn as sns top_words [w for w, _ in counter.most_common(30)] co_matrix pd.DataFrame(0, indextop_words, columnstop_words) for kws in df[keywords].dropna(): words [w.strip().lower() for w in kws.split(;) if w.strip().lower() in top_words] for a, b in itertools.combinations(set(words), 2): co_matrix.loc[a, b] 1 co_matrix.loc[b, a] 1 plt.figure(figsize(14, 12)) sns.heatmap(co_matrix, cmapYlOrRd, annotFalse) plt.savefig(cooccurrence_heatmap.png, dpi200, bbox_inchestight)从热力图能看出几个明显的簇transformer、large language model、attention 经常一起出现diffusion model、generative model、score-based 是一簇graph neural network、message passing、node classification 是一簇。这种结构化的信息比单纯词云有用得多。5. 投稿趋势和审稿结果的交叉分析5.1 投稿量按时间分布把submit_date按天聚合画一条时间线能看到投稿截止日前几天的爆发式增长。ICLR 的 deadline 通常是某天下午最后 48 小时的投稿量能占到总量的 30% 以上。这个数据对准备投稿的人有参考价值越早投审稿人分到的稿子越少理论上单篇得到的审稿时间更充裕。daily df.groupby(df[submit_date].dt.date).size() daily.plot(kindline, figsize(14, 5), titleICLR 2024 每日投稿量) plt.savefig(daily_submissions.png, dpi200, bbox_inchestight)5.2 关键词与录用结果的关系OpenReview 上能拿到审稿评分和最终决定Accept/Reject。我把 keywords 和 venue 交叉统计每个关键词对应的录用率。样本量太小的关键词出现少于 20 次直接过滤掉避免统计噪声。df[accepted] df[venue].str.contains(Poster|Oral|Spotlight, caseFalse, naFalse) kw_stats [] for kw in top_words: mask df[keywords].str.contains(kw, caseFalse, naFalse) subset df[mask] if len(subset) 20: kw_stats.append({ keyword: kw, count: len(subset), accept_rate: subset[accepted].mean() }) kw_df pd.DataFrame(kw_stats).sort_values(accept_rate, ascendingFalse)结果挺有意思一些偏理论的方向optimization、generalization theory录用率相对稳定而一些特别热的方向large language model、diffusion录用率反而偏低因为投稿量太大、竞争激烈。这个结论不能直接推导因果关系但至少说明热门方向不一定好中。5.3 摘要长度和录用率顺手统计了一下摘要字数。ICLR 的摘要没有硬性字数限制但实际分布集中在 150 到 250 词之间。录用稿的平均摘要长度比被拒稿略长一点但差异不大大概 10 到 15 词。这个差异在统计上不显著所以别指望把摘要写长就能中。指标录用稿均值被拒稿均值差异摘要词数19818513关键词数量4.23.80.4标题词数11.511.20.3注意这些统计只是描述性的不能当因果结论用。摘要长可能是因为工作本身更完整而不是因为长所以中。6. 常见问题与排查技巧实录6.1 API 返回空数据怎么办最常见的原因是 invitation 写错。ICLR 每年的 invitation 格式会变2023 是ICLR.cc/2023/Conference/-/Submission2024 是ICLR.cc/2024/Conference/-/Submission。如果返回{notes: [], count: 0}先检查 invitation 拼写再用浏览器直接访问 API 地址确认。另一个原因是 offset 超限。OpenReview 对 offset 有上限超过一定值会返回空。解决办法是用cdate或者id做游标分页而不是纯 offset。6.2 JSON 解析报错怎么排查json.loads报JSONDecodeError通常是因为返回的不是 JSON而是 HTML 错误页。加一层判断try: data resp.json() except json.JSONDecodeError: print(返回内容不是 JSON前 200 字符, resp.text[:200])如果报KeyError: value说明某条记录的 content 结构和预期不一样用safe_get兜底。6.3 词云图中文乱码wordcloud 默认字体不支持中文需要指定font_path。Windows 下用C:/Windows/Fonts/simhei.ttfMac 下用/System/Library/Fonts/PingFang.ttc。如果关键词全是英文这个问题不存在。6.4 内存不够怎么办三年数据加起来两万多条拍平之后 CSV 也就几十兆普通笔记本完全够用。如果拉更多年份或者更多会议可以用分块读取chunks pd.read_csv(iclr_all.csv, chunksize5000) for chunk in chunks: process(chunk)6.5 常见问题速查表问题可能原因解决方法API 返回空invitation 错误核对年份和格式JSON 解析失败返回 HTML打印 resp.text 前 200 字符KeyError value结构不一致用 safe_get 兜底词云中文乱码字体不支持指定 font_path统计结果异常重复记录按 id 去重录用率计算偏差样本太小过滤出现少于 20 次的关键词7. 几个实操心得整套流程跑下来最大的感受是数据清洗比分析本身花的时间多。API 拉数据可能就十分钟但拍平、去重、统一格式、处理缺失值断断续续搞了一下午。如果重来一次我会先把一条记录的完整结构打印出来确认每个字段的类型和嵌套层级再写批量处理代码。另外词云图适合做展示不适合做分析。真正有用的结论都来自词频表、共现矩阵和交叉统计。词云可以放在报告第一页吸引眼球但后面的表格才是干货。还有一个细节OpenReview 的数据是动态的今天拉的和明天拉的可能不一样因为作者可以修改投稿、审稿人可以改评分。如果要做可复现的分析一定要把原始 JSON 存档并在文章里注明拉取日期。最后分享一个小技巧如果只想快速看某个方向的热度不用拉全量数据用 OpenReview 的搜索接口按关键词过滤返回的 count 字段直接就是投稿量。这个方法适合做快速验证但不适合做完整统计。