用Python爬虫与数据分析透视arXiv:从API抓取到学术趋势图
发布时间:2026/9/26 12:45:17来源:尧图网络
这篇记录我在 GitHub 上开源过的那个“arXiv 趋势透视”小项目的完整过程其实代码量不大但整个思路从数据采集到最后的趋势判断每一步都值得拆开讲一讲。先说明白这项目是干嘛的用 Python 写爬虫从 arXiv 公开接口拉取论文元数据存到 SQLite 里再用 Pandas 和 Matplotlib 做统计分析最后输出学术趋势图。适合对爬虫感兴趣但是不想上手太难的读者也适合想跟踪研究热点、又不想天天刷网页的科研人员。1. 项目整体设计与思路拆解1.1 为什么一定要抓数据而不依赖搜索页如果只是查某一篇论文、看某个作者近期发了什么arXiv 官方网站的搜索功能完全够用。但“透视科研趋势”这个需求本质上不是单点查询而是要对成百上千篇论文的元数据做统计。手点搜索页只能得到零散快照而且翻页、复制、整理的过程异常痛苦。举个不太恰当的例子你不可能靠一天刷几百个短视频来判断整个内容行业的走向更靠谱的做法是拉出全站的播放数据做聚合分析。论文趋势也是一样的道理。数据抓下来之后能做几类以前做不了的事。一是时间维度上的提交量变化某个分类是持续变热还是阶段性爆发肉眼看不出来画成折线图就一目了然。二是分类热度对比cs.LG、cs.CV、cs.CL 这几块现在的体量已经远超其他方向但是具体到月级别的增速只有算过才知道。三是标题关键词的词频变化最近一年哪个技术名词出现频率暴增这也是判断热点迁移的重要信号。这些分析全部建立在结构化数据集的基础上搜索页的信息形态很难支撑。所以这个项目的核心设计思路是先解决“数据怎么稳定获取”再解决“数据怎么清洗入库”最后才是“数据怎么分析”。很多人一上来就急着写抓取代码数据库设计、字段清洗都懒得做结果数据越抓越乱分析的时候才回头补课反而更费时间。1.2 选型核心官方 API 为主网页爬虫为辅这里有个很多新手容易忽视的关键点arXiv 其实提供了官方 API接口地址是 export.arxiv.org返回数据是 Atom XML 格式。这意味着大部分抓取任务根本不需要从论文详情页去解析 HTML直接用接口就能拿全标题、摘要、作者、分类、时间这些元数据。我以官方 API 为主网页爬虫只在补字段时才用比如某些老论文的 DOI 信息缺失需要去详情页确认。官方 API 和网页爬虫的对比我实际使用下来的感受是这样的对比项arXiv 官方 API网页 HTML 爬虫合规性官方提供有明确使用指南页面没有明确禁止但需要克制数据格式Atom XML结构化程度高需要解析 HTML结构易受改版影响稳定性很稳定字段变化少改版就要改解析代码覆盖范围元数据齐全适合批量统计可以拿到 PDF 下载地址、LaTeX 源码链接开发成本低请求一次就是一篇列表中需要处理标签嵌套和反爬我建议所有初学者都从 API 入手不是说网页爬虫没用而是能把 API 用的足够好再考虑网页解析。API 的请求方式简单、返回结构固定等于官方替你降低了门槛。至于要不要用 Selenium 这种浏览器自动化工具在这个项目里完全没有必要因为目标是静态的 XML 数据不是 JavaScript 动态渲染的页面。启动浏览器去抓静态页面就像开卡车去送一封平信笨重且不划算。1.3 字段设计与分析目标动手写代码之前先定清楚需要哪些字段以及这些字段要支撑什么样的分析。我最终确定的字段清单如下字段名来源用途arxiv_idAPI 返回 ID主键、去重published提交时间时间趋势分析updated最后更新时间追踪论文更新title标题关键词挖掘summary摘要文本分析、后续扩展authors作者列表作者活跃度统计primary_category主分类分类热度categories全部分类跨学科分析doiDOI 编号文献关联journal_ref期刊信息后续增值为什么主分类和全部分类要分开存因为 arXiv 允许一篇论文打多个分类标签但第一个标签才是它的主领域。统计“cs.CL 最近有多少篇论文”时如果用全部分类会重复计数同一篇论文既挂在 cs.CL 又挂在 cs.LG会被算两次按主分类统计虽然保守一点但口径干净不会误导判断。字段设计的原则是“宁缺毋滥”。一开始我也想把论文的 PDF 下载地址、评论数、受监督的元数据都抓下来后来发现很多分析根本用不到。数据仓库的精髓不在于字段越多越好而在于字段能覆盖你真正要回答的问题。2. 环境准备与工具选型2.1 Python 环境与开发工具链Python 版本我建议直接用 3.10 以上原因倒不是一定要用新语法而是新版本的 SSL 库和依赖兼容性更好跑网络请求的时候不容易出莫名其妙的证书报错。如果机器上已经装了其他版本也不影响用虚拟环境隔离就够。python -m venv .venv source .venv/bin/activate # Windows 下是 .venv\Scripts\activate这一步非常重要别上去就pip install一堆库到全局环境。虚拟环境相当于给你的项目单独开了一个小房间以后换项目换依赖不会互相打架。我见过太多人为了环境问题浪费一两个小时最后发现是不同项目的依赖版本冲突。开发工具用 VS Code 还是 PyCharm 都行。VS Code 轻量配合 Pylance 插件提示也够用PyCharm 对数据分析和调试更友好。我自己用的是 VS Code因为跑这种中小型脚本根本不需要太重型的 IDE。配置 Python 环境时只要在 VS Code 里选中虚拟环境的解释器路径即可终端里激活好 .venv 之后运行命令会自动带上环境。2.2 依赖库清单与取舍理由这个项目的核心依赖其实只有 4 个requests、lxml、pandas、matplotlib。SQLite 不需要额外安装Python 标准库自带sqlite3模块直接import就能用。requests 负责 HTTP 请求这个没什么争议。lxml 用来解析 Atom XML比标准库的 ElementTree 解析速度快而且 XPath 语法更顺手处理带命名空间的 XML 时要省心很多。pandas 是数据分析的主力加载 SQLite 数据、做分组统计、生成透视表都靠它。matplotlib 用来画图虽然看起来不如可视化大屏那么炫酷但是胜在轻量、可控性高输出 PNG 就能直接用到报告里。有人会问为什么不用 Scrapy。Scrapy 是一个完整的爬虫框架有并发调度、中间件、管道这些机制非常适合做大规模分布式抓取。但这个任务的量级其实是“最近一年的一个分类”或者“某几个分类的元数据”单机、串行、几百个请求就搞定了。用 Scrapy 属于杀鸡用牛刀而且它的学习曲线比直接写 requests 脚本陡不少。还有人问为什么不用 Selenium前面说了目标页面是服务端直接返回的静态 XML没有动态加载用浏览器自动化去模拟点击就太绕了。安装命令合起来是pip install requests lxml pandas matplotlib装完之后可以用python -c import requests, lxml, pandas, matplotlib验一下没报错就说明环境没问题。这个检查步骤虽小但能避免跑脚本到一半才发现缺库的尴尬。3. 核心实现取数、解析、入库的完整套路3.1 先看懂 arXiv API 的请求格式arXiv API 的请求地址是https://export.arxiv.org/api/query通过 GET 参数构造查询条件。最常见的几个参数是search_query、start、max_results、sortBy、sortOrder。search_query支持很多种检索语法最常用的是分类检索和时间区间检测。比如要抓 2024 年全年的计算机视觉论文查询条件长这样cat:cs.CV AND submittedDate:[20240101000000 TO 20241231235959]submittedDate后面的字段格式是YYYYMMDDHHMMSS必须精确到这个位数少一位都可能识别不了。start表示从第几条开始max_results表示一批返回多少条这两个参数配合起来就是分页逻辑。我最初踩过一个坑直接把分类和时间拼进 URL然后忘了让 requests 做 URL 编码。分类名中间的冒号、时间区间的中括号这些都还好但某些年份字符串里带逗号就不行。正确的做法是把 query 作为search_query参数的值让 requests 的params自动编码不要自己手动拼 URL。import requests BASE_URL https://export.arxiv.org/api/query def build_params(query, start0, max_results100): return { search_query: query, start: start, max_results: max_results, sortBy: submittedDate, sortOrder: descending, }3.2 分页拉取与请求节奏控制一次请求最多返回多少条官方没有把话说死但我自己的经验是max_results设置成 100 比较稳妥。设置的太多响应包太大解析耗时不说万一请求超时就白白浪费了。分页逻辑很简单第一页start0第二页start100第三页start200直到拿到的条数小于max_results为止。完整的分页拉取代码大概是这样的import time import requests def fetch_page(query, start, max_results100): params build_params(query, start, max_results) resp requests.get(BASE_URL, paramsparams, timeout30) resp.raise_for_status() return resp # 按每页 100 条拉取 10 页 for page in range(10): start page * 100 resp fetch_page(query, start) parse_page(resp) print(fprocessed page {page 1}, start{start}) time.sleep(3)这里的time.sleep(3)是我加的精髓。第一次跑脚本我只顾着快间隔设成了 0.5 秒结果还没跑完一半就收到了 429 状态码被迫整个脚本重启。之后我老老实实把间隔拉到 3 秒稳定跑完几千条不出问题。3 秒不快不慢一页 100 条300 条论文也就 9 秒完全没有效率焦虑的必要。3.3 XML 解析与字段抽取细节API 返回的 Atom XML 带命名空间用 lxml 解析时需要把命名空间映射关系提前声明好。返回的一个entry节点对应一篇论文我们要从里面抽取前面定义好的那些字段。from lxml import etree ATOM_NS http://www.w3.org/2005/Atom ARXIV_NS http://arxiv.org/schemas/atom ns {atom: ATOM_NS, arxiv: ARXIV_NS} def parse_page(resp): root etree.fromstring(resp.content) papers [] for entry in root.xpath(./atom:entry, namespacesns): title .join(entry.xpath(./atom:title/text(), namespacesns)).strip() summary .join(entry.xpath(./atom:summary/text(), namespacesns)).strip() authors [a.strip() for a in entry.xpath(./atom:author/atom:name/text(), namespacesns)] published entry.xpath(./atom:published/text(), namespacesns)[0].strip() updated entry.xpath(./atom:updated/text(), namespacesns)[0].strip() paper_url entry.xpath(./atom:id/text(), namespacesns)[0].strip() arxiv_id paper_url.rsplit(/, 1)[-1] base_id arxiv_id.split(v)[0] primary_cat entry.xpath(./arxiv:primary_category/term, namespacesns) primary_cat primary_cat[0] if primary_cat else all_cats entry.xpath(./atom:category/term, namespacesns) papers.append({ arxiv_id: base_id, title: clean_text(title), summary: clean_text(summary), authors: |.join(authors), published: published, updated: updated, primary_category: primary_cat, categories: ,.join(all_cats), }) return papers def clean_text(text): return .join(text.split())有几个细节值得注意。第一title和summary的 text 节点里可能夹着换行和多个空格直接 strip 不够要先把所有连续空白替换成单个空格所以上面单独写了clean_text。第二作者列表我存成竖线分隔的字符串没有用 JSON因为后面分析的时候一般只需要统计数量不愿意把列表拆成多行增加复杂度。第三arxiv_id从 URL 中提取的时候带版本号比如2401.00001v1我统一去掉v后面的部分这样同一篇论文的 v1、v2 版本不会在数据库里变成两行。3.4 增量抓取与断点续传设计如果只是跑一次看个新鲜增量抓取可以不做。但只要你打算长期维护这个数据就一定要考虑“增量”和“断点续传”。增量抓取的核心思路是用时间字段做筛选。比如你上周已经抓了 2024 年全年的数据这周想更新就去请求submittedDate:[20250101000000 TO 20250131235959]把 1 月份新增的论文拉回来。这样不会把历史上万条数据重新抓一遍既节省时间也减少对 API 服务端的压力。断点续传的底线是哪怕脚本中途崩溃重启后也不会重复插入大量数据。我的做法是把arxiv_id设成 SQLite 表的主键插入的时候用INSERT OR REPLACE。这样重复抓同一篇论文时后抓到的数据会直接覆盖旧数据不会产生重复行。就算脚本跑到第 37 页断了重启时从第 0 页重新跑也没关系已经存在的记录会原地更新不存在的一批批插进去最终结果完全一致。3.5 SQLite 建表与写入数据库建表语句如下够用且清晰CREATE TABLE IF NOT EXISTS papers ( arxiv_id TEXT PRIMARY KEY, title TEXT, authors TEXT, published TEXT, updated TEXT, primary_category TEXT, categories TEXT, summary TEXT, fetched_at TEXT DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX IF NOT EXISTS idx_published ON papers(published); CREATE INDEX IF NOT EXISTS idx_category ON papers(primary_category);fetched_at字段是我后来加的用来记录每行数据是什么时候入库的。这个字段平时看不到作用但当你需要排查“为什么某条数据是旧的”时它非常有用。两个索引分别建立在时间和分类字段上因为后续分析大概率会按这两个维度筛选索引能显著加快查询速度。写入代码也很简单import sqlite3 conn sqlite3.connect(arxiv_papers.db) cur conn.cursor() def insert_papers(papers): rows [ ( p[arxiv_id], p[title], p[authors], p[published], p[updated], p[primary_category], p[categories], p[summary], ) for p in papers ] cur.executemany( INSERT OR REPLACE INTO papers (arxiv_id, title, authors, published, updated, primary_category, categories, summary) VALUES (?, ?, ?, ?, ?, ?, ?, ?), rows, ) conn.commit()每抓完一页就 commit 一次不要攒到最后统一提交。因为一旦程序崩溃或者被 CtrlC 终止已经 commit 的页都能保住没有 commit 的只丢最后一页代价很小。4. 数据透视从“抓回来”到“看得懂”4.1 提交量时间趋势用滚动平均消除噪音数据入库之后剩下的就是从 SQLite 读取并做分析。第一步先看宏观趋势也就是“这个领域到底在变热还是变冷”。import pandas as pd df pd.read_sql_query(SELECT * FROM papers, conn) df[published] pd.to_datetime(df[published]) df[ym] df[published].dt.to_period(M) trend df.groupby(ym).size().sort_index() smooth trend.rolling(3, min_periods1).mean()trend是每个月的论文提交数量smooth是它的 3 个月滚动平均。为什么要滚动平均因为单个月份的波动太大年底大家冲量、年初放假提交量掉得厉害单独看某个月很容易误判。滚动平均相当于把最近三个月的值取一个均值曲线更平滑反映出来的趋势更接近真实方向。我拿 cs.CV 分类试跑了一组数据年度趋势确实能看到几个明显的波峰比如每年 6 月和 12 月左右论文量会上升这与会议截稿时间有很大的相关性。这个结论不算新颖但亲手从自家数据库里算出来那种感觉和听别人讲完全不同。4.2 分类热度与研究方向迁移接下来按主分类统计热度top_cats df[primary_category].value_counts().head(10) cat_trend pd.crosstab(df[ym], df[primary_category])[top_cats.index]value_counts()统计每个主分类下的论文总数取前 10 名形成榜单。crosstab生成一个以年月为行、分类为列的交叉表每个单元格里是该分类在当月的论文提交量。这个交叉表就是研究“方向迁移”的原料。我看过一组很有意思的数据某些老牌分类的论文总量依然是头部但近几个月的月提交量已经增长缓慢而另一些相对小的分类月度数据像是坐了火箭这往往意味着新的学术热点正在形成。这类信息如果只看总量排行榜很容易被“它们总量还很高”的表面现象迷惑交叉表能帮你抓住结构性的变化。另外要注意primary_category是主分类一篇论文可能同时挂了很多分类但始终只归属到它最核心的那个。统计的时候用主分类就不会把同一篇论文重复计入多个领域分析口径更干净。4.3 标题关键词词频与热点捕捉标题是论文的浓缩关键词的兴起和退潮经常比正文内容更早反映科研方向的转变。这一步做的是标题层面的文本挖掘逻辑很简单把所有论文标题拆成单词干掉停用词和短词再用 Counter 统计词频。from collections import Counter import re STOPWORDS { the, of, and, for, in, on, with, by, to, from, a, an, is, are, at, as, that, this, we, our, using, via, } def tokenize(text): text re.sub(r[^a-zA-Z0-9\s], , text.lower()) return [w for w in text.split() if w not in STOPWORDS and len(w) 2] word_counter Counter() for title in df[title].dropna(): word_counter.update(tokenize(title)) top_words word_counter.most_common(20)这里我没有用 jieba因为 arXiv 的标题几乎全是英文用正则就足够了。如果哪天想把范围扩展到中文论文再引入中文分词也不迟。停用词列表要根据数据情况不断补比如“using”、“via”这类非常常见的词放在词频榜里不仅没有信息量还会抢占前排位置必须清掉。词频分析最大的价值不是告诉你“neural”出现多少次而是你可以把今年和去年的 Top 词放一起比较。某个词去年排二十几位、今年冲到前五说明这个方向正在快速升温。我跑的时候发现一些跟大模型评估、对齐相关的词排名涨得非常快这与近一段时间的业界动态是吻合的。4.4 可视化三张图讲清一个趋势分析完成之后用 matplotlib 输出三张图分别是提交量趋势、分类 Top 榜和关键词词频榜。我把它们拼在一张画布上这样一张图就能概括整体情况。import matplotlib.pyplot as plt plt.rcParams[figure.dpi] 150 fig, axes plt.subplots(1, 3, figsize(16, 4.2)) trend.plot(axaxes[0], color#1f77b4, lw1.5, labelmonthly) smooth.plot(axaxes[0], color#d62728, lw2.5, label3-month avg) axes[0].set_title(Monthly submissions and 3-month average) axes[0].set_xlabel(publish month) axes[0].set_ylabel(paper count) axes[0].legend() top_cats.plot(kindbarh, axaxes[1], color#2ca02c) axes[1].set_title(Top categories) axes[1].invert_yaxis() word_series pd.Series(dict(top_words)).sort_values() word_series.plot(kindbarh, axaxes[2], color#9467bd) axes[2].set_title(Top title keywords) fig.tight_layout() fig.savefig(arxiv_trend.png, dpi200, bbox_inchestight)这里刻意全部使用英文标签原因很简单matplotlib 默认字体不支持中文如果强行显示中文会出现方框乱码。为了避免中文字体配置的麻烦直接统一用英文标签视觉上也更协调。如果你想在正式报告里用中文可以通过plt.rcParams[font.sans-serif] [SimHei]之类的方式去配置中文字体但不同系统字体不一样需要额外测试。三张图的布局不是随便排的。趋势图回答“什么时候在变热”分类图回答“哪个方向在变热”词频图回答“因什么主题在变热”三者是一个递进关系。图做出来不只是为了好看而是要能直接回答一个问题某个学科的科研趋势正在发生什么变化。5. 常见问题与排查技巧实录5.1 429 限流与请求频率控制这是我遇到的第一道坎。第一次跑脚本时我完全没有请求频率的概念循环里一个requests.get接着下一个间隔只有 0.2 秒。大约跑到第 200 多次请求时响应不再返回 XML而是返回了一个 429 状态码提示访问过于频繁。解决方式分两层。第一层是主动放慢节奏在循环里加time.sleep(3)。第二层是做重试机制一旦遇到 429等待时间呈指数增长再重试而不是死心不改地立刻请求。import time import random def safe_get(params, retries5, base_wait2): for attempt in range(retries): try: resp requests.get(BASE_URL, paramsparams, timeout30) if resp.status_code 200: return resp if resp.status_code 429: wait base_wait * (2 ** attempt) random.uniform(0, 1) print(f429 received, wait {wait:.2f}s) time.sleep(wait) continue resp.raise_for_status() except requests.ConnectionError: time.sleep(base_wait) raise RuntimeError(request failed after retries)指数退避的规律很简单第一次重试等 2 秒第二次等 4 秒第三次等 8 秒以此类推。这样既给了服务端喘息时间也大大提升了重试成功的概率。现在这个函数已经成了我所有网络请求模块里的标配不是只有 arXiv 才需要任何公开 API 都可能限流。5.2 字段缺失、时间解析与摘要清洗arXiv 的元数据整体很干净但老论文和某些交叉领域论文会出现字段缺失的情况。比如doi可能为空primary_category在极少数 entry 里也可能取不到。写解析代码时凡是取 XPath 结果数组的都要判断一下数组长度再取值不然就是IndexError: list index out of range。时间字段是另一个容易出问题的点。API 返回的时间格式是2024-01-01T00:00:00Z末尾带Z表示 UTC 时间。Pandas 的to_datetime能直接处理这种字符串但如果你自己写datetime.fromisoformat要注意它不认Z最好先替换成00:00。摘要文本的清洗要更仔细。标题和摘要里经常有 LaTeX 公式、换行符、奇怪的连字符直接用strip()处理不干净。我写了个clean_text把所有连续空白包括换行符和多个空格压成一个空格。这个函数我建议后续扩展正则把\\begin{equation}之类的公式标记也一并去掉让文本分析的结果更聚焦在自然语言上。5.3 长任务中断与断点恢复全量抓取一个分类最近十年的数据大概需要请求 120 多页。串行加延时的情况下耗时大约 6 分钟不算太长但足够让人在等待时手滑按到 CtrlC或者碰到网络闪断。我第一次跑全量数据时没想过中断的事结果跑到第 30 多页网络断开前面拿到的数据因为没 commit 全部丢了后悔得不行。后来我学乖了做了两处调整。第一每处理完一页就conn.commit()确保每一批数据都落盘。第二通过数据库主键去重重启脚本后从头开始跑已存在的数据自动被覆盖更新新数据正常插入整个过程不产生重复。如果你跑的量大到需要记录抓取进度可以加一张 meta 表保存当前的start位置但我觉得对单个分类、几千条数据来说这个复杂度完全没有必要。SQLite 主键天然就是最好的断点续传机制用最简单的方案解决 80% 的需求。5.4 并发提速的正确打开方式有人看到串行 3 秒一条会忍不住想开多线程加速。我不反对但必须先给你打预防针arXiv 不是为你一个脚本服务的抓太快了被限流是小事给官方服务造成压力就不好了。如果你实在想提速可以尝试ThreadPoolExecutor(max_workers3)这种程度的轻量并发每个线程内部依然要遵守 3 秒间隔。但我要说的是这个项目的瓶颈根本不在网络请求速度而在数据清洗和分析思路。跑全量的一页数据只要 0.1 秒反而是清洗和分析经常要反复调代码花的时间多一个量级。先把串行流程跑通再考虑并发优化才是正道。另外劝一句不要写那种“一秒钟几十个请求”的暴力脚本官网的服务条款里明确请大家控制请求频率。爬虫不是越快越好可持续的自制力比瞬时速度更值钱。6. 后续升级方向与个人经验6.1 从一次性脚本到定时任务数据抓下来只是一次性的快照但科研趋势是动态的。如果你想长期跟踪最好把脚本改造成可以定时跑的形式。Linux 上用 cron 是最简单的# 每周一早上 8 点更新一次 0 8 * * 1 cd /path/to/project .venv/bin/python update.py update.log 21Windows 上可以用系统自带的“任务计划程序”。核心逻辑都一样写一个update.py只增量抓取过去一周的论文然后更新 SQLite 数据库。定时任务跑一个月后你手上就会有连续的历史数据这时候再看趋势说服力比单次快照强得多。GitHub Actions 也可以干这个活把脚本放在仓库里配置一个 schedule 事件每周自动跑一遍并把生成的图和 CSV 提交回仓库。好处是不依赖本地机器坏处是如果数据量大一点GitHub 仓库的体积会膨胀。我的建议是先本地跑等流程稳定了再考虑自动化平台的迁移。6.2 从关键词到更像样的“趋势雷达”基本的词频榜有一个致命缺陷它只能告诉你高频词是什么不能告诉你哪个词正在“变热”。一个词去年的基数就很高今年继续保持高频率这不算热度上升真正值得关注的是那些去年同期排名很低、今年突然冲到前面的词。想做这种“热度变化”分析思路其实不复杂。把数据按时间切分成两个区间分别统计 Top N 关键词然后比较每个词的排名变化。排名上升幅度大的词就是潜力股。我建议把这类分析的间隔设成半年或一年太短了噪音太大三个月内的随机波动会干扰判断。你甚至可以更进一步把“关键词热度”和“提交量趋势”叠在一起看。如果某个关键词的词频暴增同时对应分类的提交量也在快速上涨那基本可以确认这个方向正处于快速上升期。这时候再想去投论文或者做技术选型心里就有底多了。6.3 数据边界和别忘了克制最后想聊一点自己的体会。arXiv 的 API 是公开的抓取它的元数据在合规层面没有问题但“能抓”不等于“可以随便抓”。任何公开数据源都有它的承载上限做爬虫的人尤其要有边界感。我的建议是三条。第一控制请求频率永远不要在循环里裸奔式发请求。第二尽量只抓必需的字段不需要的摘要就别额外拉取了减少数据冗余的同时也缩短请求时间。第三如果做的是个人小项目尽量不要去抓全文 PDF那些内容体量大且版权边界复杂元数据已经足够支撑趋势分析。我自己在扩展这个项目的时候试过抓取 PDF 全文做更深入的文本挖掘后来放弃了。原因不是技术做不到而是投入产出比太低PDF 解析涉及版面识别、图表处理算力消耗极大而论文标题和摘要提供的信息已经非常足够。把简单的事情做扎实比把复杂的事情做糊弄更有价值。根据我跑了几个月的经验建议你先挑一个分类、只抓最近一年的数据把从抓取、入库到出图这条路完整走通之后再决定要不要扩大范围。一上来就想着把十年全量数据拉下来很容易在早期阶段被各种问题劝退。数据量小的时候调试和验证都快等流程成熟了再上规模才是做这类数据分析项目的最稳路径。
网站建设高端定制企业官网