Google Scholar 是研究人员、学生和学者查找各学科学术文章、会议论文、学位论文和引用信息的首选平台。其搜索结果页面是一份简洁的书目记录:每条条目包含论文标题、作者、发表机构与时间、被引次数以及来源链接。这使得 Google Scholar 成为文献综述、引用分析和追踪研究议题演变的有力起点。
本指南展示如何用 Python 可靠地抓取 Google Scholar 搜索结果。你将构建一个可直接运行的小型爬虫:通过 Crawling API 获取渲染后的结果页面,用 BeautifulSoup 解析每条结果,并将干净的记录导出为 JSON 和 CSV。本教程仅涉及任何人都能在结果页面上直接看到的公开书目数据,文末的合法性说明并非套话,请在对实际数据量运行此脚本前务必阅读。
你将构建什么
一个 Python 脚本,接收 Google Scholar 搜索 URL,通过 Crawling API 获取 HTML,并为页面上的每条结果提取结构化记录。我们以查询"Data Science"为例,从每条结果中提取以下字段:
- 标题 条目中显示的论文或书籍标题。
- 作者 从标题下方署名行解析出的作者姓名。
- 出版来源 署名行中所注明的期刊、会议、出版社或来源。
- 年份 署名行中包含的发表年份。
- 引用次数 结果条目下方"Cited by"链接中显示的被引次数。
- 链接 结果标题所指向的目标 URL。
为什么普通请求在 Google Scholar 上会失败
如果你从脚本向 Google Scholar 结果 URL 发送一个普通 HTTP 请求,很少能获得你在浏览器中看到的干净页面。Scholar 对自动化流量监控严格。不像真实浏览器的请求会遭遇 CAPTCHA 验证、被引导至验证页面,或在少量请求后被限速,而来自单一数据中心 IP 的反复查询更是立竿见影地暴露身份。此外,该页面在某些地方依赖脚本执行,静态请求可能缺少浏览器渲染才会呈现的内容。
因此,一个可用的 Scholar 爬虫在单次请求中需要具备两点:IP 地址看起来像真实访客,以及在页面需要时能进行浏览器渲染。你可以自行搭建无头浏览器加轮换住宅代理的方案,但维护这套系统才是真正的工作量所在。Crawling API 将两者整合为一次调用:你发送 URL,它从可信住宅 IP 获取页面并在需要时进行渲染,然后返回可供解析的完整 HTML。
Google Scholar 是最快开始对来自同一地址的自动化查询提供 CAPTCHA 的目标之一。Crawling API 轮换住宅 IP 并在服务端处理这些验证挑战,因此你无需自行采购代理或破解 CAPTCHA。你可以从 1,000 次免费请求开始,无需信用卡。
前置条件
在编写任何代码之前,你需要准备好以下几项,都不需要太长时间。
Python 基础。 你需要能够编写和运行 Python 脚本,并能用 pip 安装包。如果你对 BeautifulSoup 不熟悉,我们的 Python 中使用 BeautifulSoup 指南涵盖了本教程所依赖的解析基础知识。
Python 3.8 或更高版本。 用 python --version 确认你的版本。如果还没有安装,可以从 python.org 或 Anaconda 等发行版获取。
Crawlbase 账号和令牌。 注册后,打开控制台并复制你的请求令牌。Crawlbase 提供两种令牌类型:Normal(普通)和 JavaScript;对于 Google Scholar,应使用 Normal 令牌。前 1,000 次请求免费。请像对待密码一样保护令牌:它用于验证你的请求,切勿提交到版本控制系统中。
项目设置
创建一个虚拟环境以隔离项目依赖,然后安装爬虫所需的两个库。
python --version python -m venv scholar_env source scholar_env/bin/activate pip install requests beautifulsoup4
在 Windows 上,用 scholar_env\Scripts\activate 替代 source 那行来激活虚拟环境。两个依赖各司其职:requests 向 Crawling API 发送 HTTP 请求,beautifulsoup4 解析返回的 HTML,让你可以用 CSS 选择器提取各字段。
第 1 步:通过 Crawling API 获取页面
首先获取 HTML。编写一个小型 crawl() 函数,将目标 URL 连同令牌发送给 Crawling API,检查 Scholar 页面是否以 200 状态码返回,并返回 HTML 正文。在解析前先检查状态码,可以让失败信息清晰可见,而不是无声消失。
import json import requests API_TOKEN = "YOUR_CRAWLBASE_TOKEN" # replace with your token API_ENDPOINT = "https://api.crawlbase.com/" def crawl(url): params = {"token": API_TOKEN, "url": url} response = requests.get(API_ENDPOINT, params=params) response.raise_for_status() data = json.loads(response.text) if data["original_status"] != 200: raise Exception(f"Unable to crawl '{url}'") return data["body"] if __name__ == "__main__": url = "https://scholar.google.com/scholar?q=Data+Science" html = crawl(url) print(html[:500])
API 返回一个 JSON 信封,因此用 json.loads 加载响应并读取两个字段:original_status 是 Google Scholar 本身返回的状态码,body 是页面 HTML。对 original_status 加守卫,意味着 CAPTCHA 页面或封锁会以异常形式呈现,而不是将垃圾数据喂给解析器。搜索词通过 q 参数传递,这是 Scholar 承载查询的方式。用 python crawling.py 运行脚本,你应该能在前 500 个字符中看到真实的结果标记,这证明在编写任何选择器之前,获取步骤已经正常工作。
那个 original_status 检查之所以始终读到 200,是因为请求本就以真实访客的身份抵达 Google Scholar。Crawling API 从轮换住宅 IP 获取页面,吸收 Scholar 对自动化流量投放的 CAPTCHA 挑战,并在页面需要浏览器时进行渲染,然后将完整 HTML 交还给你。你无需自行运营无头浏览器集群或采购住宅代理池。先用免费额度指向一个公开结果 URL 试试看。
第 2 步:检查结果结构
开始解析前,在浏览器中打开一个 Google Scholar 结果页面,右键单击某条结果并选择"检查",查看每条条目的结构。每条结果都被包裹在一个携带 data-rp 属性(记录其位置)的 div.gs_r 元素中。在该容器内部:标题位于 h3.gs_rt 标题中,目标链接是其内部的锚点;署名行(作者、出版来源、年份)位于 div.gs_a 元素中;摘要片段在 div.gs_rs 中;"Cited by"被引次数则出现在 div.gs_fl 下方的页脚链接中。这些类名就是解析器的选择器目标。
唯一需要额外处理的字段是署名行。Google Scholar 将作者、出版来源和年份打包成单个 gs_a 字符串,以破折号分隔,例如 H Wickham, M Çetinkaya-Rundel - 2023 - books.google.com。在下一步中,我们将拆分这个字符串,分别提取这三部分。
第 3 步:用 BeautifulSoup 解析结果
拿到 HTML 后,将其加载到 BeautifulSoup 中,并用上一步的选择器提取每条结果。署名行解析器以破折号为分隔符拆分 gs_a 文本,然后扫描中间和末尾片段以查找四位数年份,从而将作者、出版来源和年份分别存入各自的字段。
import re from bs4 import BeautifulSoup def parse_byline(text): # gs_a packs "authors - publication, year - host" parts = [p.strip() for p in text.split(" - ")] authors = parts[0] if parts else "" publication = parts[1] if len(parts) > 1 else "" year = None match = re.search(r"\b(19|20)\d{2}\b", text) if match: year = match.group() publication = re.sub(r",?\s*" + year, "", publication).strip() return authors, publication, year def parse_citations(result_item): for a in result_item.select("div.gs_fl a"): text = a.get_text(strip=True) if text.startswith("Cited by"): return int(text.replace("Cited by", "").strip()) return 0 def parse_google_scholar(html): soup = BeautifulSoup(html, "html.parser") results = [] for item in soup.select("div.gs_r[data-rp]"): heading = item.find("h3", class_="gs_rt") link = item.select_one("h3.gs_rt > a") byline = item.find("div", class_="gs_a") if not heading: continue authors, publication, year = "", "", None if byline: authors, publication, year = parse_byline(byline.get_text(strip=True)) results.append({ "position": int(item["data-rp"]), "title": heading.get_text(strip=True), "authors": authors, "publication": publication, "year": year, "citations": parse_citations(item), "link": link["href"] if link else None, }) return results
选择器 div.gs_r[data-rp] 匹配每个结果容器,并跳过没有位置属性的布局块。对于每个容器,h3.gs_rt 提供标题,其内部的锚点提供链接;div.gs_a 馈入署名行解析器;parse_citations 遍历 div.gs_fl 中的页脚链接,找到"Cited by"条目并返回数字,没有引用的论文返回 0。直接从 data-rp 读取位置与 Scholar 本身分配的排名一致。if not heading: continue 守卫可防止杂散标记混入输出。
Google 偶尔会修改 Scholar 的标记。gs_rt、gs_a 和 gs_fl 等类名已相当稳定,但请将其视为起始模板而非合同。如果某个字段在所有结果中都返回空值,请在浏览器开发者工具中重新检查当前页面并更新选择器。定期维护选择器是任何生产级爬虫的正常工作。
第 4 步:整合并导出 JSON 和 CSV
现在将获取和解析步骤连接成一个可运行的脚本,然后将结构化输出写入 JSON 和 CSV。JSON 保留嵌套结构便于程序使用,CSV 则可直接导入电子表格或用 pandas 进行文献综述分析。设置 ensure_ascii=False 可确保含有重音字符的作者姓名在文件中正确显示。
import csv import json import requests from bs4 import BeautifulSoup from scholar_parser import parse_google_scholar # the parser from step 3 API_TOKEN = "YOUR_CRAWLBASE_TOKEN" API_ENDPOINT = "https://api.crawlbase.com/" FIELDS = ["position", "title", "authors", "publication", "year", "citations", "link"] def crawl(url): params = {"token": API_TOKEN, "url": url} response = requests.get(API_ENDPOINT, params=params) response.raise_for_status() data = json.loads(response.text) if data["original_status"] != 200: raise Exception(f"Unable to crawl '{url}'") return data["body"] def save_json(results, path="scholar_results.json"): with open(path, "w", encoding="utf-8") as f: json.dump(results, f, ensure_ascii=False, indent=2) def save_csv(results, path="scholar_results.csv"): with open(path, "w", newline="", encoding="utf-8") as f: writer = csv.DictWriter(f, fieldnames=FIELDS) writer.writeheader() writer.writerows(results) def main(): query = "Data Science" url = f"https://scholar.google.com/scholar?q={query.replace(' ', '+')}" html = crawl(url) results = parse_google_scholar(html) save_json(results) save_csv(results) print(f"Saved {len(results)} results to JSON and CSV") if __name__ == "__main__": main()
用 python main.py 运行完整脚本。它获取"Data Science"的结果页面,为每条条目提取一条记录,并写入 scholar_results.json 和 scholar_results.csv 两个文件。只需更换查询词,解析器就能处理任何返回内容,这几个函数已经够用了。
输出示例
你将得到一个有序的结果对象列表,每个对象包含已解析的标题、作者、出版来源、年份、被引次数和链接,可直接写入 JSON、CSV 或数据库。
[ { "position": 0, "title": "[BOOK][B] R for data science", "authors": "H Wickham, M Çetinkaya-Rundel, G Grolemund", "publication": "books.google.com", "year": "2023", "citations": 8421, "link": "https://books.google.com/books?id=TiLEEAAAQBAJ" }, { "position": 1, "title": "Data science and its relationship to big data and data-driven decision making", "authors": "F Provost, T Fawcett", "publication": "Big data", "year": "2013", "citations": 2510, "link": "https://www.liebertpub.com/doi/abs/10.1089/big.2013.1508" } ]
CSV 镜像包含相同列,每条结果一行,首行为 position,title,authors,publication,year,citations,link。这种格式正是大多数文献综述工作流所需要的,因为你可以直接在电子表格中按被引次数排序或按年份筛选。
处理分页
单次查询只获取一页是演示;真实任务需要翻页深入结果。Google Scholar 通过 start 查询参数分页,以 10 为步长递增偏移量:start=10 是第二页,start=20 是第三页,以此类推,每页十条结果。结构保持不变:构建每页 URL,通过 Crawling API 获取,用同一函数解析。在请求之间设置停顿,而不是密集循环发送。
import time def fetch_paginated_results(base_url, max_pages=5): all_results = [] for page in range(max_pages): start = page * 10 # 10 results per page url = f"{base_url}&start={start}" html = crawl(url) all_results.extend(parse_google_scholar(html)) time.sleep(3) return all_results base_url = "https://scholar.google.com/scholar?q=Data+Science" results = fetch_paginated_results(base_url, max_pages=5) print(f"Collected {len(results)} results across 5 pages")
Crawling API 返回的任何 5XX 响应均不计费,因此重试被封锁或不可用的 URL 不会消耗配额。如果你希望通过轮换代理池自行路由流量而不使用托管 API,Smart AI Proxy 提供与 Crawling API 相同的住宅 IP 轮换,作为直接替换的代理端点。要存储超过单次运行的结果,请在抓取每一页时将记录写入 SQLite 等数据库(以标题和链接为键),而不是将所有数据保留在内存中。
保持不被封锁
即使有可信 IP 处理,Google Scholar 仍会监测爬虫特征流量,而且它比大多数搜索目标更快地启用 CAPTCHA。养成以下习惯可以让抓取任务健康运行。
- 控制请求频率。 密集循环抓取结果页面是最快触发验证挑战的方式。分散请求,改变查询词,而不是全速翻页同一词条。
- 依赖 IP 轮换。 住宅 IP 池将请求分散到许多真实用户地址,使单个 IP 不会触及限额。Crawling API 为你处理这一点;如果你自建方案,这正是需要重点把握的部分。
- 读懂状态码。 如果一次运行开始返回 CAPTCHA 或验证页面,说明当前的频率或 IP 层级已不够用。应将其视为退后的信号,而非可以忽略的噪音。
- 字段为空时重新检查选择器。 Scholar 偶尔会更改其标记。如果结果停止解析,请在开发者工具中打开当前页面并更新选择器。
更完整的应对方案,请参阅如何在不被封锁的情况下抓取网站。如果你同时需要抓取主 Google 索引,我们的如何抓取 Google 搜索页面指南涵盖了常规 SERP 结构,而Python 通用爬虫指南则介绍了本教程所依赖的基础知识。
抓取 Google Scholar 合法吗?
抓取 Google Scholar 是否被允许,取决于 Google 的服务条款、你所在的司法管辖区以及你对数据的用途。Google Scholar 的条款对自动化访问有所限制,并明确不鼓励出于商业目的的抓取行为,因此无论工具多么谨慎,自动化采集都可能违反这些条款。本文中的任何代码都不能改变这一点,它只是让技术层面可行。请阅读 Scholar 的条款及其 robots.txt,并将两者视为采集范围的边界。
以下几条原则值得遵守。只采集公开书目数据:标题、作者、出版详情、年份、被引次数以及任何人无需账号即可在结果页面上看到的链接。保持请求量足够低,不要给 Scholar 的服务器造成压力,采用节制的抓取频率而非全速运行。关键一点:这些条目是关于论文的元数据,而非论文本身。不要使用爬虫拉取或转发位于付费墙或出版商许可协议之后的文章全文;这与读取公开引用元数据是完全不同的性质,不在本文讨论范围内。
本指南刻意将范围限定在公开结果页面,因为这是让工作保持可辩护性的边界。Google 没有为此类访问提供广泛可用的官方 Scholar API,因此没有经过授权的大规模端点可作为退路,这更是理由充分地保持适度规模并尊重网站所声明的规则。如果你的项目需要低量公开元数据以外的内容,正确的路径是获得许可的书目数据集或出版商的官方 API,而不是一个更聪明的爬虫。
核心要点
- Scholar 很快就会封锁爬虫特征流量。 它一旦发现来自单一 IP 的自动化查询就会迅速投放 CAPTCHA,因此你需要可信住宅地址才能看到真实结果。
- Crawling API 使用真实 IP 获取数据。 将 URL 发送给它,它轮换住宅 IP 并在服务端处理 CAPTCHA,然后将完整 HTML 返回给你解析。
-
BeautifulSoup 负责提取。 选择每个
div.gs_r[data-rp],然后读取标题、链接和gs_a署名行,并将署名行拆分为作者、出版来源和年份。 -
被引次数和分页很简单。 从
div.gs_fl读取"Cited by"次数,并以 10 为步长递增start偏移量翻页,请求之间暂停。 - 坚守公开元数据。 遵守 Scholar 的服务条款和 robots.txt,控制采集量,切勿拉取付费墙背后的全文或个人数据。
常见问题
为什么普通请求在 Google Scholar 上会失败或返回 CAPTCHA?
Scholar 会标记不像真实浏览器的流量,并迅速对来自单一数据中心 IP 的重复查询进行限速,因此普通脚本往往会收到 CAPTCHA 或验证页面,而不是你在浏览器中看到的结果。通过 Crawling API(使用轮换住宅 IP 并在服务端处理 CAPTCHA 挑战)获取页面,可以让请求看起来像普通访客,从而获得真实的结果页面。
如何用 Python 抓取 Google Scholar 数据?
使用 requests 库将搜索 URL 发送给 Crawling API,然后用 BeautifulSoup 解析返回的 HTML。选择每个 div.gs_r[data-rp] 结果,从 h3.gs_rt 读取标题,从 div.gs_a 读取署名行,从 div.gs_fl 读取被引次数。关于解析基础知识,请参阅我们的 BeautifulSoup 指南。
我可以从 Google Scholar 结果中提取哪些字段?
本教程从每条结果中提取标题、作者、出版来源、年份、被引次数和链接,以及来自 data-rp 属性的位置。作者、出版来源和年份都来自单个 gs_a 署名行,由解析器拆分。请坚守公开书目元数据,避免拉取付费全文。
如何获取每篇论文的被引次数?
每条结果的页脚(div.gs_fl 块)在论文有引用时包含一个"Cited by N"链接。解析器扫描这些链接,找到以"Cited by"开头的那个,读取末尾的数字;对于尚无引用的论文返回 0。
如何翻页获取更多 Google Scholar 结果?
使用 start 查询参数,以 10 为步长递增偏移量:start=10 是第二页,start=20 是第三页,以此类推,每页十条结果。用偏移量构建每页 URL,通过 Crawling API 获取,用同一函数解析,并在请求之间暂停几秒,以节制抓取速度而非猛烈轰炸。
抓取到 Google Scholar 数据后可以分析吗?
可以。将结果导出为 CSV 或 JSON,然后加载到 pandas 等工具中进行分析。由于记录包含被引次数和年份,你可以按影响力排序、按时效筛选,或绘制某一议题随时间的产出增长图,这正是文献综述所需要的功能。
大规模爬取任何站点,无需与基础设施对抗。
Crawlbase 负责处理代理、指纹和 CAPTCHA,让你的团队专注于交付数据流水线,而非维护爬取管道。1,000 次请求免费,无需信用卡。
