实战分享Python爬虫,如何实现高效解析 Web of Science 文献数据并导出 CSV

在科研信息分析、选题调研、竞品技术追踪、论文计量研究中,Web of Science(下文简称 WoS)一直是高价值数据来源。它收录规范、元数据结构完整,尤其适合做文献统计分析:作者、机构、关键词、被引、出版年、研究方向等字段都比较标准化。

但很多同学在实际操作时会遇到一个问题:如何高效、稳定、合规地提取文献数据,并整理成可分析的 CSV?

本文给你一套完整的 Python 实战方案,目标是:

  1. 明确 WoS 数据抓取的合规边界;
  2. 设计可维护的爬虫与解析流程;
  3. 实现从页面/接口响应中抽取核心文献字段;
  4. 处理分页、重试、去重、反爬、编码、异常;
  5. 最终导出高质量 CSV,便于后续用 pandas、Excel、R、SPSS、VOSviewer 做分析。

重要说明:本文仅用于合法合规的数据处理与科研用途。请严格遵守 WoS 平台服务条款、机构订阅协议、robots 规范及当地法律。若平台提供官方 API,优先使用官方 API。


一、先讲清楚:技术可行 ≠ 合规可用

在写任何爬虫前,先做三件事:

  • 阅读目标平台使用条款(Terms of Use)
  • 确认是否允许自动化采集
  • 优先评估是否有官方 API 或导出功能

对于 WoS 这类商业数据库,很多学校/机构是通过订阅访问,通常会对批量抓取有限制。

因此实践建议是:

  1. 优先官方导出/API:最稳、最合规。
  2. 若做自动化采集,只在授权范围内,控制频率,避免高并发冲击。
  3. 不绕过登录安全机制,不规避访问控制,不抓取无授权内容。

二、需求拆解:我们到底要抓哪些字段?

为了后续结构化分析,通常建议最少抓以下字段:

  • title(标题)
  • authors(作者)
  • source(期刊/会议)
  • year(年份)
  • doi
  • abstract(摘要)
  • keywords(关键词)
  • affiliations(机构)
  • times_cited(被引次数)
  • document_type
  • wos_id / accession_number
  • url(详情页链接)

CSV 输出建议一行一条文献,复杂字段(作者、关键词)用分号拼接。


三、技术路线设计:页面抓取还是接口抓取?

常见有两条路线:

路线A:抓网页 HTML 再解析

  • 工具:requests + BeautifulSoup/lxml
  • 优点:直观、好理解
  • 缺点:页面结构改版就容易失效;动态加载场景麻烦

路线B:抓浏览器真实请求的 JSON 接口(推荐)

  • 工具:开发者工具 Network + requests/httpx
  • 优点:数据结构稳定、解析简单、效率高
  • 缺点:可能涉及鉴权 token、cookie、header 维护

在现代站点里,很多"页面内容"本质由接口返回 JSON 渲染。
实战优先选择"接口层数据",可大幅提升稳定性。


四、项目结构建议(可维护)

建议把代码拆成 6 个模块,而不是全写一个文件:

text

wos_spider/ ├── config.py # 配置:关键词、页数、延迟、输出路径 ├── session.py # 会话初始化、headers、cookie管理 ├── fetcher.py # 请求发送、重试、限速 ├── parser.py # JSON/HTML字段解析 ├── pipeline.py # 去重、清洗、导出CSV └── main.py # 主流程编排

这样的好处:后续换接口、加字段、改导出格式都更容易。


五、实战代码:从请求到导出 CSV(示例模板)

说明:以下代码是"通用结构示例",接口 URL 和参数需按你实际授权环境抓包确认后替换。不要用于未授权抓取。

1)安装依赖

bash

pip install requests pandas tenacity tqdm

2)配置文件 config.py

python

QUERY = "machine learning" START_PAGE = 1 END_PAGE = 20 PAGE_SIZE = 50 SLEEP_SECONDS = 1.5 OUTPUT_CSV = "wos_records.csv" BASE_URL = "https://example.wos.api/search" # 替换为实际接口 HEADERS = { "User-Agent": "Mozilla/5.0", "Accept": "application/json", }

3)请求与重试 fetcher.py

python

import time import requests from tenacity import retry, stop_after_attempt, wait_fixed class WosFetcher: def init(self, headers=None, sleep_seconds=1.0): self.s = requests.Session() if headers: self.s.headers.update(headers) self.sleep_seconds = sleep_seconds @retry(stop=stop_after_attempt(3), wait=wait_fixed(2)) def get_page(self, url, params): resp = self.s.get(url, params=params, timeout=20) if resp.status_code != 200: raise Exception(f"HTTP {resp.status_code}") time.sleep(self.sleep_seconds) return resp.json()

4)解析模块 parser.py

python

def safe_get(d, path, default=None): cur = d for p in path: if isinstance(cur, dict) and p in cur: cur = cur[p] else: return default return cur def parse_record(item): title = safe_get(item, ["title"], "") authors = safe_get(item, ["authors"], []) or [] authors = "; ".join([a.get("name", "") for a in authors if isinstance(a, dict)]) keywords = safe_get(item, ["keywords"], []) or [] if isinstance(keywords, list): keywords = "; ".join([str(k) for k in keywords]) affiliations = safe_get(item, ["affiliations"], []) or [] if isinstance(affiliations, list): affiliations = "; ".join([ aff.get("org", "") if isinstance(aff, dict) else str(aff) for aff in affiliations ]) return { "wos_id": safe_get(item, ["id"], ""), "title": title, "authors": authors, "source": safe_get(item, ["source"], ""), "year": safe_get(item, ["year"], ""), "doi": safe_get(item, ["doi"], ""), "abstract": safe_get(item, ["abstract"], ""), "keywords": keywords, "affiliations": affiliations, "times_cited": safe_get(item, ["times_cited"], 0), "document_type": safe_get(item, ["doc_type"], ""), "url": safe_get(item, ["url"], ""), }

5)数据清洗与导出 pipeline.py

python

import pandas as pd def clean_and_export(records, output_csv): df = pd.DataFrame(records) # 基础清洗 for col in df.columns: df[col] = df[col].astype(str).str.replace(r"\s+", " ", regex=True).str.strip() # 去重:优先 DOI,其次标题 if "doi" in df.columns: df = df.sort_values("doi").drop_duplicates(subset=["doi"], keep="first") df = df.drop_duplicates(subset=["title"], keep="first") # 年份与被引转数值(失败置空) if "year" in df.columns: df["year"] = pd.to_numeric(df["year"], errors="coerce") if "times_cited" in df.columns: df["times_cited"] = pd.to_numeric(df["times_cited"], errors="coerce").fillna(0).astype(int) df.to_csv(output_csv, index=False, encoding="utf-8-sig") return df

6)主程序 main.py

python

from tqdm import tqdm from config import QUERY, START_PAGE, END_PAGE, PAGE_SIZE, BASE_URL, HEADERS, OUTPUT_CSV, SLEEP_SECONDS from fetcher import WosFetcher from parser import parse_record from pipeline import clean_and_export def run(): fetcher = WosFetcher(headers=HEADERS, sleep_seconds=SLEEP_SECONDS) all_records = [] for page in tqdm(range(START_PAGE, END_PAGE + 1), desc="Fetching"): params = { "q": QUERY, "page": page, "size": PAGE_SIZE } data = fetcher.get_page(BASE_URL, params=params) items = data.get("records", []) # 按实际返回结构调整 for item in items: try: all_records.append(parse_record(item)) except Exception as e: print(f"[ParseError] page={page}, err={e}") df = clean_and_export(all_records, OUTPUT_CSV) print(f"Done. rows={len(df)}, file={OUTPUT_CSV}") if name == "main": run()


六、关键实战技巧:让爬虫"高效且稳定"

1)限速与随机延迟

不要连续高频请求。建议基础延时 1~3 秒,并加入随机抖动,降低触发风控概率。

2)失败重试与断点续跑

网络波动是常态。

  • 对 429/5xx 做重试
  • 每抓一页就落盘(JSONL/CSV 临时文件)
  • 中断后从最后页继续

3)会话保持

很多站点依赖会话态(cookie、token)。

用 requests.Session() 统一管理,必要时定期刷新 token。

4)字段容错

真实数据会有缺项。解析时不要假设字段一定存在,统一使用 safe_get。

5)编码统一

导出 CSV 用 utf-8-sig,在 Windows Excel 打开中文更稳。


七、常见问题与解决方案

问题1:返回 403/401

  • 可能缺少鉴权头、cookie 失效、权限不足
  • 检查登录态与请求头是否完整
  • 优先走官方 API 鉴权方式

问题2:抓到的是空数据

  • 参数名不对(如 q、page、size 实际不同)
  • 返回结构层级变化(records 不在顶层)
  • 查询条件在 URL 编码后失真

问题3:速度慢

  • 减少不必要字段请求
  • 用接口 JSON 而非 HTML 解析
  • 合规前提下做小规模并发(谨慎)

问题4:CSV 打开乱码

  • 改为 encoding="utf-8-sig"
  • 或用支持 UTF-8 的工具(VSCode、LibreOffice、pandas)

问题5:重复数据多

  • 先按 doi 去重,再按 title + year 辅助去重
  • 标题统一大小写、去多余空格后再比对

八、数据质量提升:从"能用"到"好用"

采集完成只是第一步,分析前建议做标准化:

  1. 作者名标准化(如 Zhang, Wei vs Wei Zhang)
  2. 机构名归一化(同一机构多写法合并)
  3. 关键词清洗(单复数、缩写归并)
  4. 年份异常值处理(空值、未来年份)
  5. DOI 格式校验(正则匹配)

可以额外输出两份文件:

  • raw_wos.csv(原始留档)
  • clean_wos.csv(清洗后分析用)

九、进阶:异步抓取与批量分析(可选)

当数据量较大时,可以考虑:

  • httpx + asyncio 异步请求(仍需限速)
  • 增加代理池(合规前提)
  • 落地 SQLite/PostgreSQL 而非一次性 CSV
  • 用 Airflow/Prefect 定时任务化
  • 结合 NLP 做主题建模、关键词共现网络

但请注意:规模越大,越要先确认授权边界


十、官方 API 优先策略(强烈建议)

如果你的机构有 WoS API 使用权限,建议直接走官方路线:

  • 字段文档清晰
  • 请求稳定可预期
  • 版本升级可追踪
  • 合规风险更低
  • 方便持续化数据管道建设

你可以把本文的代码结构几乎原样迁移到 API,只需替换请求地址和解析器字段映射。

编程语言C++https://github.com/aidehualeo/tech-bjwmfu/blob/main/README.md ++c语言的魅力

编程语言C++https://github.com/aidehualeo/tech-vrstri/blob/main/README.md ++c语言的魅力

编程语言C++https://github.com/aidehualeo/tech-uelhoa/blob/main/README.md ++c语言的魅力

"Python 爬虫 + 文献数据"真正的专业能力,不是写出一个能跑的脚本,而是做到这四点:

  1. 合规:尊重平台规则与授权边界;
  2. 稳定:可重试、可恢复、可维护;
  3. 结构化:字段清晰、编码统一、可直接分析;
  4. 可扩展:后续可接数据库、可做自动化任务。

围绕这些原则,你就能把 WoS 文献采集从"临时脚本"升级为"科研数据工程流程"。

最终产出的 CSV,不只是一个表格,而是你后续做计量分析、热点挖掘、知识图谱构建的高质量起点。

相关推荐
redaijufeng2 小时前
网络爬虫学习:应用selenium获取Edge浏览器版本号,自动下载对应版本msedgedriver,确保Edge浏览器顺利打开。
爬虫·学习·selenium
MeAT ITEM2 小时前
爬虫基础之爬取某基金网站+数据分析
爬虫·数据挖掘·数据分析
23471021272 小时前
4.14 学习笔记
笔记·python·学习
徐小夕2 小时前
PDF无限制预览!Jit-Viewer V1.5.0开源文档预览神器正式发布
前端·vue.js·github
STLearner2 小时前
WSDM 2026 | 时空数据(Spatial Temporal)论文总结
人工智能·python·深度学习·机器学习·数据挖掘·智慧城市·推荐算法
a9511416422 小时前
如何加固SQL集群防注入_实施网络层访问控制策略
jvm·数据库·python
xiaotao1312 小时前
01-编程基础与数学基石:Python错误与异常处理
开发语言·人工智能·python
2401_835956812 小时前
mysql处理大量更新场景_InnoDB MVCC与MyISAM对比
jvm·数据库·python
WangJunXiang62 小时前
Haproxy搭建Web群集
前端