文本获取:Web爬虫基础、学术API使用
文本从哪来?这是动手做知识图谱之前第一个要解决的问题。
很多人一上来就扎进爬虫,到处找代理、调浏览器模拟、绕反爬。嗯,我不反对这种方式,但说实话,在学术知识图谱这个领域,**80%的高质量数据都可以通过学术API拿到**。直接爬网页往往性价比不高——你花了大量时间处理页面结构,最终拿到的还是非结构化文本,还得自己拆。
这一章我会带你摸清两条路:什么时候用爬虫,什么时候调API。这两条路不冲突,关键看场景。
Web爬虫:什么时候用?怎么用?
学术API覆盖不了所有场景。比如你想爬某个学院的全部教师主页,或者某些会议网站的PDF附件链接,这些数据API一般不提供。这时候就只能上爬虫了。
我第一次写爬虫踩过一个坑——直接用浏览器开发者工具拷贝XPath,跑起来全挂。为什么?因为页面是动态拼接的,拷贝的XPath压根匹配不上真实DOM。后来我养成了一个习惯:先用 requests 拿到页面原始HTML,再用 lxml 的 etree.HTMLParser 手动构造解析树,一层一层试。笨是笨了点,但稳。
HTTP请求:基础中的基础
Python里做请求,你选哪个?我建议新人直接从 requests 库入手。API设计友好,出错信息也比较清晰。
import requests
url = "https://example.edu/projects"
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
}
response = requests.get(url, headers=headers, timeout=10)
if response.status_code == 200:
html_content = response.text
print(f"页面长度: {len(html_content)} 字符")
else:
print(f"请求失败: {response.status_code}")
解析:从HTML到结构化文本
拿到HTML之后,怎么抽取出你要的论文标题、摘要、作者列表?
我用得最多的组合是 lxml.html + CSS选择器。相比XPath,CSS选择器更直观,尤其对于前端开发者熟悉的class和id选择方式。
from lxml import html
tree = html.fromstring(html_content)
# 提取所有论文标题(假设页面中标题在 .paper-title 的h3标签内)
titles = tree.cssselect(".paper-title h3")
for t in titles:
print(t.text_content().strip())
# 提取摘要(.abstract 下的p标签)
abstracts = tree.cssselect(".abstract p")
for a in abstracts:
print(a.text_content().strip()[:200])
这里有个细节我要特意说一下:text_content() 拿到的是所有子文本的拼接,包括子标签里的内容。如果你只想取当前标签的直接文本,用 text 属性。
爬虫的“度”:别把别人服务器爬崩了
我见过有人写了一个for循环,不设延迟,直接对同一台服务器发起每秒几十次请求。结果IP被永久封禁。倒不是对方小气,是你的爬虫行为太像攻击了。
我自己通常会加三样东西:
- 请求间隔:至少1秒,最好2-3秒
- 重试机制:502/503时等几秒再试一次
- robots.txt检查:别碰被禁止的路径
import time
import random
def crawl_with_gap(url, min_delay=2):
time.sleep(min_delay + random.uniform(0, 1))
return requests.get(url, headers=headers)
# 使用示例
for page_id in range(1, 11):
resp = crawl_with_gap(f"https://example.edu/papers?page={page_id}")
# 处理resp...
学术API:高效获取结构化数据
做知识图谱我最喜欢用API。为什么?因为它直接返回JSON,字段清晰,不用你从HTML里费劲地抽。 Semantic Scholar API、OpenAlex、CrossRef,这些都是业内常用的。
先看Semantic Scholar API。这个API对学术文本抽取来说特别好用——它不只能返回论文基本信息,还能返回引用关系、影响力数据、甚至嵌入向量。
基本调用
import requests
base_url = "https://api.semanticscholar.org/graph/v1/paper/search"
params = {
"query": "knowledge graph construction",
"limit": 10,
"fields": "title,abstract,authors,year,externalIds"
}
response = requests.get(base_url, params=params, timeout=10)
if response.status_code == 200:
data = response.json()
for paper in data["data"]:
print(f"标题: {paper['title']}")
print(f"年份: {paper['year']}")
print(f"摘要: {paper['abstract'][:150] if paper['abstract'] else '无'}")
print("---")
else:
print(f"API错误: {response.status_code}")
核心技巧:用 fields 参数控制返回字段,减少不必要的数据传输。学术API通常有速率限制,Semantic Scholar API的免费额度是100次/分钟,对大多数项目都够用。
批量获取:用论文ID查询
有时候你已经有论文列表(比如从某个会议网站爬到的DOI或ArXiv ID),想批量拉元数据。这时候用批量查询接口最合适。
papers_ids = [
"CorpusId:215416146",
"ArXiv:1806.01261",
"DOI:10.1145/3340531.3412037"
]
bulk_url = "https://api.semanticscholar.org/graph/v1/paper/batch"
payload = {"ids": papers_ids}
resp = requests.post(bulk_url, json=payload, timeout=15)
if resp.status_code == 200:
results = resp.json()
for paper in results:
if paper:
print(paper.get("title", "Unknown"))
else:
print(f"批量查询失败: {resp.status_code}")
引用关系抽取:知识图谱的核心
做知识图谱离不开引用关系。Semantic Scholar API提供了专门引用图接口,可以拿到论文的引用和被引用列表。
paper_id = "CorpusId:215416146"
citations_url = f"https://api.semanticscholar.org/graph/v1/paper/{paper_id}/citations"
params = {
"fields": "title,authors,year",
"limit": 20
}
resp = requests.get(citations_url, params=params, timeout=10)
if resp.status_code == 200:
citation_data = resp.json()
for item in citation_data["data"]:
citing_paper = item["citingPaper"]
print(f"引用来源: {citing_paper['title']} ({citing_paper['year']})")
爬虫 vs API:怎么选?
我把两者的适用场景整理成了一个表,你选方案前可以参考:
| 维度 | Web爬虫 | 学术API |
|---|---|---|
| 数据类型 | 非结构化HTML / PDF文本 | 结构化JSON / XML元数据 |
| 获取成本 | 需要解析,开发量大 | 直接解析JSON,开发量小 |
| 速率限制 | 取决于网站策略,容易被封 | 有明确API限流文档 |
| 覆盖范围 | 几乎所有公开网页 | 仅限于该平台收录的文献 |
| 典型场景 | 教授主页、PDF附件、不在API内的内容 | 论文元数据、引用网络、影响力指标 |
从我的经验来看,混合方案往往最实用。先用API拿到元数据和引用关系,再用爬虫补充那些API覆盖不到的文本内容。两条腿走路,比单靠哪一条都快。