Python脚本实现CSV到WOS格式转换及CiteSpace文献分析全流程
1. 从“奇葩”需求到自动化方案:为什么需要CSV转WOS?
做文献计量分析的朋友,尤其是用CiteSpace的,估计都从Web of Science(WOS)上下载过数据。标准的流程是:在WOS上检索,导出为“纯文本”或“全记录与引用的参考文献”格式,得到一堆.txt文件,然后直接扔进CiteSpace里分析。但有时候,现实总爱开点玩笑。
我就遇到过这么个情况。当时一门研究方法课的老师,不知道是出于什么考虑,从WOS上检索了2000多篇文献,自己捣鼓了一番,把数据整理成了一个CSV表格发给我们,让我们用CiteSpace做聚类分析。拿到文件我一看就懵了:CiteSpace只认WOS、Scopus、CNKI等特定格式的文本数据,它可不认识你这规规矩矩的CSV表格啊。每一行是一条文献记录,每一列是字段,像标题、作者、摘要、关键词、发表年份等等,整整齐齐。这格式给人看是清晰了,但对CiteSpace来说,就是一堆“乱码”。
手动把两千多条记录,按照WOS那种一行字段名、一行字段值,中间用空格隔开,每条记录以“PT J”开头、“ER”结尾的格式去改?想想都觉得是噩梦。这种重复、机械、易出错的工作,正是Python脚本大显身手的地方。所以,我一气之下(或者说,灵机一动)决定写个Python脚本,把这个“奇葩”的CSV文件,重新转换回CiteSpace能吃的“正经”WOS格式。
这个过程,本质上是一个数据格式的逆向工程与重构。我们有一个结构化的、表格化的数据源(CSV),需要将它转换成一种特定的、带标记的文本格式(WOS)。这不仅解决了眼前的课程作业问题,更是一个通用的数据管道技巧。以后只要你拿到结构化的文献数据(比如从其他数据库导出的CSV,或者自己用爬虫整理的表格),都可以用类似的思路,把它“喂”给CiteSpace、VOSviewer等只认特定格式的软件。接下来,我就把自己踩坑和填坑的全过程,掰开揉碎了分享给你,哪怕你代码基础一般,跟着做也能搞定。
2. 解密WOS数据格式:知其然,更要知其所以然
在动手写代码之前,我们得先搞清楚目标长什么样。你得先弄明白WOS导出的数据文件是什么结构,才能知道我们的脚本要输出什么。
你从WOS官网,选择“导出”->“纯文本文件”或者“全记录与引用的参考文献”,下载下来的通常是一个名为savedrecs.txt之类的文本文件。用记事本打开它,你会发现它不是简单的表格。它的结构是这样的:
FN Thomson Reuters Web of Science
VR 1.0
PT J
TI The title of paper one
AU Author A
Author B
AB This is the abstract of paper one.
C1 [Author A] University; [Author B] Company
PY 2023
ER
PT J
TI The title of paper two
AU Author C
AB This is the abstract of paper two.
PY 2022
ER
EF
我来解释一下这些“暗号”:
FN和VR:这是文件的“抬头”,标明文件来源和版本,通常出现在文件最开头。PT J:表示记录类型是“Journal”(期刊文章),这是每条文献记录的起始标志。CiteSpace就靠这个识别一条新记录开始了。TI,AU,AB,PY等:这些是字段标签。TI代表标题,AU代表作者,AB代表摘要,PY代表发表年份。WOS有几十个这样的固定标签。- 字段值:紧跟在字段标签后面(通常是一个空格隔开),就是该字段的具体内容。比如
TI The title of paper one。 ER:这是每条文献记录的结束标志。PT J和ER成对出现,像括号一样包住一条完整记录。- 记录分隔:每条
ER后面,通常会跟一个空行,再开始下一条记录,这样视觉上更清晰。 EF:这是整个文件的结束标志,放在所有文献记录之后。
关键点在于:WOS格式是“纵向”的,它是一条记录接一条记录地排列,每条记录内部是“字段标签 + 值”的线性列表。而我们的CSV是“横向”的,它是一条记录占一行,各个字段作为列横向展开。我们的转换,核心就是一次“行列转置”,把CSV的“行”变成WOS里一条记录的“多行”,同时把CSV的表头(列名)替换成WOS认识的字段标签。
所以,转换任务可以拆解成几个明确的步骤:
- 字段映射:把我们CSV里的列名(如“文章标题”、“作者”),翻译成WOS的官方标签(如
TI,AU)。 - 数据转置:把CSV的矩阵数据“旋转”90度,让每一列(代表一个字段的所有值)变成WOS格式里需要逐条记录拼接的部分。
- 格式组装:在每条记录前后加上
PT J和ER,在文件头尾加上FN、VR和EF,并确保字段标签和值之间用空格(而不是逗号)分隔。 - 输出为文本:最终生成一个
.txt文件。
理清了思路,我们就可以打开代码编辑器,开始动手了。
3. 手把手实战:Python脚本编写全流程
我建议你在电脑上新建一个专属文件夹,比如叫 csv_to_wos,把所有的脚本和输入输出文件都放在这里,管理起来方便。我们的操作会分成几个小步骤,每个步骤一个脚本,这样逻辑清晰,也便于调试。
3.1 第一步:准备原料——清洗与映射你的CSV数据
首先,你得有原料。把老师给的、或者你自己整理的CSV文件,放到项目文件夹里。为了保险起见,我强烈建议你先用Excel或WPS打开这个CSV文件,做一次简单的“体检”:
- 检查编码:确保文件是
UTF-8编码,否则中文可能会乱码。用记事本“另存为”时可以查看和选择编码。 - 处理特殊字符:看看摘要、标题里有没有多余的换行符(
\n)、引号,这些可能会干扰CSV解析。在Excel里可以查找替换。 - 统一列名:确保你的CSV第一行是表头,并且列名明确。比如“Title”, “Authors”, “Abstract”, “Year”等。如果列名是中文,比如“文章标题”,也没关系,我们在脚本里做映射。
接下来,就是最关键的字段映射。你需要一张“翻译表”。WOS的官方字段标签很多,我们常用的是下面这些,你可以根据自己CSV里的列名进行对应:
| 你的CSV列名 (示例) | WOS 字段标签 | 含义 |
|---|---|---|
| Title, 文章标题 | TI | 标题 |
| Authors, 作者 | AU | 作者 |
| Abstract, 摘要 | AB | 摘要 |
| Keywords, 关键词 | DE | 作者关键词 |
| Keywords Plus, 扩展关键词 | ID | WOS扩展关键词 |
| Year, 出版年 | PY | 出版年 |
| Source, 来源期刊 | SO | 期刊名称 |
| Volume, 卷 | VL | 卷 |
| Issue, 期 | IS | 期 |
| Pages, 页码 | BP | 起始页码 |
| DOI | DI | 数字对象标识符 |
| Addresses, 机构 | C1 | 作者地址 |
| ... | ... | ... |
小技巧:你可以在WOS上随便导出一两条记录,打开看看它用了哪些标签,对照着建立你自己的映射字典。这个字典是我们后续脚本的“核心配置”。
假设我的CSV文件列名是中文,我准备了一个映射字典。现在,我们先不急着在代码里映射,而是用Excel手动操作,更直观。把CSV文件用Excel打开,直接修改第一行的表头单元格,把“文章标题”改成“TI”,“作者”改成“AU”,以此类推。改完后另存为一个新的CSV文件,比如叫 input_mapped.csv。这一步是让数据“准备就绪”。
3.2 第二步:核心转换——用Python实现行列转置与记录组装
原料准备好了,开始烹饪。在项目文件夹里新建一个Python脚本,命名为 step1_transpose.py。这个脚本要完成的任务是:读取映射好表头的CSV,将其转换成每条记录以PT J开头、ER结尾的中间格式。
# step1_transpose.py
import csv
def csv_to_wos_intermediate(input_csv, output_txt):
"""
将表头已映射为WOS标签的CSV文件,转换为中间WOS格式文本。
参数:
input_csv: 输入的CSV文件路径(表头应为WOS标签,如TI, AU)
output_txt: 输出的文本文件路径
"""
with open(input_csv, 'r', encoding='utf-8-sig') as csvfile: # 注意编码,utf-8-sig处理BOM
reader = csv.DictReader(csvfile) # 使用DictReader,方便按列名读取
fieldnames = reader.fieldnames # 获取所有字段标签(WOS标签)
all_records_lines = [] # 用来存储最终所有行的列表
# 遍历CSV的每一行(即每一条文献记录)
for row in reader:
record_lines = [] # 存储当前这条记录的所有行
record_lines.append('PT J') # 每条记录以PT J开始
# 遍历每个字段标签
for field in fieldnames:
value = row[field]
if value and str(value).strip(): # 如果字段值非空
# 将“字段标签”和“字段值”组合成一行
line = f'{field} {value}'
record_lines.append(line)
record_lines.append('ER') # 每条记录以ER结束
record_lines.append('') # 记录后加一个空行,更美观
# 将当前记录的所有行,添加到总列表中
all_records_lines.extend(record_lines)
# 写入输出文件
with open(output_txt, 'w', encoding='utf-8') as txtfile:
# 先写入文件头
txtfile.write('FN Thomson Reuters Web of Science\n')
txtfile.write('VR 1.0\n\n')
# 再写入所有记录行
txtfile.write('\n'.join(all_records_lines))
# 最后写入文件尾
txtfile.write('\nEF')
print(f"转换完成!中间文件已保存至: {output_txt}")
print(f"共处理了 {sum(1 for _ in open(input_csv, 'r', encoding='utf-8-sig')) - 1} 条记录。") # 粗略计算记录数
# 使用示例
if __name__ == '__main__':
input_file = 'input_mapped.csv' # 你修改好表头的CSV文件
output_file = 'intermediate_output.txt'
csv_to_wos_intermediate(input_file, output_file)
运行这个脚本 (python step1_transpose.py),你会得到 intermediate_output.txt。用记事本打开看看,应该已经很像WOS格式了:有FN/VR头,每条记录有PT J和ER包裹,字段标签和值在同一行。但是,这里有一个关键的细节问题:我们CSV里的值,如果本身包含逗号,在写入时会被加上引号,或者导致格式错乱。而且,WOS格式中,一个字段值如果很长(比如摘要),它是可以自然换行的,但字段标签只出现一次。我们上面的脚本简单地将一行CSV记录的所有字段堆叠起来,对于多作者等情况,可能需要更精细的处理(比如WOS中每个作者单独一行,但都以AU开头)。不过别担心,对于CiteSpace的基本分析(如关键词、机构、作者共现),这个格式通常已经足够被识别。
3.3 第三步:格式精修——处理多值字段与最终清理
上一步的输出可能已经能用,但为了更规范,我们处理两个常见问题:
- 作者字段:在CSV里,所有作者可能都在一个单元格里,用分号隔开,如“Author A; Author B; Author C”。在WOS格式中,更标准的做法是每个作者单独成行,像这样:
AU Author A AU Author B AU Author C - 地址字段:同样,多个机构地址也可能挤在一个单元格里。
我们来写一个精修脚本 step2_refine.py,专门处理像AU(作者)、C1(地址)这类可能需要拆分的字段。
# step2_refine.py
def refine_wos_file(input_txt, output_txt, fields_to_split=None, separator=';'):
"""
精修WOS格式文件,将指定字段的多值按分隔符拆分。
参数:
input_txt: 输入的中间WOS文本文件路径
output_txt: 输出的精修后文本文件路径
fields_to_split: 需要拆分的字段标签列表,如 ['AU', 'C1']
separator: 字段值内的分隔符,默认是分号
"""
if fields_to_split is None:
fields_to_split = ['AU', 'C1'] # 默认处理作者和地址字段
with open(input_txt, 'r', encoding='utf-8') as infile:
lines = infile.readlines()
new_lines = []
i = 0
while i < len(lines):
line = lines[i].rstrip('\n') # 去掉行尾换行符
if line.startswith('ER') or line.startswith('PT') or line == '' or line.startswith('FN') or line.startswith('VR') or line.startswith('EF'):
# 如果是记录标记、文件头尾或空行,原样保留
new_lines.append(line)
i += 1
continue
# 判断是否是待拆分的字段行
field_match = False
for field in fields_to_split:
if line.startswith(field + ' '): # 行以“字段标签+空格”开头
field_match = True
field_content = line[len(field)+1:] # 获取字段值部分
# 按分隔符拆分,并去除首尾空格
parts = [part.strip() for part in field_content.split(separator) if part.strip()]
if len(parts) > 1:
# 如果拆出多个部分,为每个部分生成新行
for part in parts:
new_lines.append(f'{field} {part}')
else:
# 如果只有一个部分或拆分失败,保留原行
new_lines.append(line)
break # 匹配到一个字段就跳出循环
if not field_match:
# 不是待拆分的字段,原样保留
new_lines.append(line)
i += 1
# 写入新文件
with open(output_txt, 'w', encoding='utf-8') as outfile:
outfile.write('\n'.join(new_lines))
print(f"精修完成!最终文件已保存至: {output_txt}")
# 使用示例
if __name__ == '__main__':
input_file = 'intermediate_output.txt'
output_file = 'final_output_for_citespace.txt'
# 指定需要拆分的字段,这里以作者(AU)和地址(C1)为例
refine_wos_file(input_file, output_file, fields_to_split=['AU', 'C1'], separator=';')
运行这个脚本后,你会得到 final_output_for_citespace.txt。打开看看,原先挤在一行的多个作者,现在是不是已经乖乖地每人独占一行了?这样就更加符合WOS原生数据的风格了。
4. 导入CiteSpace验证与效果分析
脚本跑通了,生成了一堆 .txt 文件,最关键的一步来了:它们能被CiteSpace认出来吗?让我们来实际验证一下。
首先,打开CiteSpace(这里以CiteSpace 6.3.R1版本为例)。在启动界面,你需要创建一个新的项目(Project)。给你的项目起个名字,比如“MyCSVConversionTest”,并指定一个空文件夹作为项目目录。
进入主界面后,找到 Data(数据) -> Import/Export(导入/导出)。在数据导入界面,最关键的是选择正确的数据来源(Data Source)。我们的文件是模仿WOS格式的,所以这里要选择 “Web of Science”。
接下来,点击 “Select Directory” 按钮,选择你存放最终 final_output_for_citespace.txt 文件的文件夹。注意,CiteSpace在导入WOS数据时,通常是读取一个文件夹下的所有.txt文件。所以,你可以把最终文件复制一份,或者直接把它放在一个干净的文件夹里让CiteSpace去读。
点击 “Import” 按钮,CiteSpace就会开始解析你的文本文件。如果格式基本正确,你会在下方的日志窗口看到类似 “Processing: final_output_for_citespace.txt... Records loaded: 2000” 的成功信息,这表明它成功识别并加载了你转换的2000条记录。
导入成功后,你就可以像使用常规WOS数据一样进行各种分析了:
- 去重:在Data菜单下进行去重操作。
- 时区划分:设置时间切片(Time Slicing),比如从1990年到2024年,每2年或5年一个切片。
- 节点类型选择:你可以选择分析关键词(Keyword)、作者(Author)、机构(Institution)、国家(Country)等。
- 运行分析:设置好阈值(如Top N per slice),点击“Go”开始运算。
实测效果如何? 根据我的经验,只要字段映射正确(特别是TI, AU, PY, SO这几个CiteSpace用于识别记录的核心字段),并且每条记录被PT J和ER正确包裹,CiteSpace的导入成功率是非常高的。转换后的数据完全可以用于生成共现网络、聚类图、时间线图、突现检测等标准分析。
你可能会遇到的一些小问题及解决办法:
- 警告“Invalid Tag”:这说明有字段标签不被CiteSpace识别。检查你的映射字典,是否用了WOS不存在的标签。可以暂时注释掉不重要的字段行。
- 作者或机构名识别混乱:如果原始CSV数据中作者名格式不统一(如“Last, First” vs “First Last”),可能会导致CiteSpace将同一个人识别成两个节点。这需要在CSV数据清洗阶段下更多功夫,比如统一姓名格式。
- 时间(PY)字段缺失或格式错误:这会影响时区分析。确保你的PY字段是四位数的年份,如“2023”。
5. 进阶技巧与脚本优化
基本的流程走通了,我们可以让这个工具变得更强大、更智能。这里分享几个我后来迭代脚本时加入的实用功能。
1. 自动化字段映射集成 我们之前是在Excel里手动改表头,其实完全可以写在Python脚本里,一步到位。创建一个 config.py 文件或者直接在主脚本里定义一个映射字典:
# 在 step1_transpose.py 开头加入
FIELD_MAPPING = {
'文章标题': 'TI',
'标题': 'TI',
'Title': 'TI',
'作者': 'AU',
'Authors': 'AU',
'摘要': 'AB',
'Abstract': 'AB',
'关键词': 'DE',
'Keywords': 'DE',
'出版年': 'PY',
'Year': 'PY',
'Publication Year': 'PY',
'来源期刊': 'SO',
'Journal': 'SO',
'卷': 'VL',
'Volume': 'VL',
'期': 'IS',
'Issue': 'IS',
'页码': 'BP',
'Pages': 'BP',
'DOI': 'DI',
'机构': 'C1',
'Addresses': 'C1',
# ... 添加更多映射
}
# 然后在读取CSV后,动态替换表头
import pandas as pd
df = pd.read_csv('your_original.csv', encoding='utf-8-sig')
df.rename(columns=FIELD_MAPPING, inplace=True)
# 删除那些没有映射的列(可选)
df = df.loc[:, df.columns.isin(FIELD_MAPPING.values())]
df.to_csv('input_mapped.csv', index=False, encoding='utf-8-sig')
这样,无论你的原始CSV列名是中文还是英文,脚本都能自动将其转换为WOS标签。
2. 处理缺失值与特殊字符 文献数据常有缺失,比如有些记录没有摘要。在生成WOS格式时,如果字段值为空,最好跳过该字段,而不是生成一个“AB ”(空值)的行。可以在组装记录行的循环中加入判断:if pd.notna(value):。
另外,文本字段(如摘要AB)里可能有换行符\n,这会在WOS文本中造成混乱。一个简单的处理办法是在写入前将其替换为空格:value = str(value).replace('\n', ' ')。
3. 批量处理与错误日志 如果你的数据来源是多个CSV文件,可以写一个循环,批量转换。同时,建议加入简单的日志功能,记录下转换成功的记录数,以及可能出错的记录ID(比如缺少必填字段TI或PY的记录),方便后续核对。
4. 封装成函数或命令行工具 你可以把整个流程封装成一个函数,甚至用 argparse 库做成命令行工具,通过指定输入文件、输出目录、映射配置文件等参数来运行,这样就更像是一个专业的小工具了。
# 示例:简单的命令行接口
import argparse
parser = argparse.ArgumentParser(description='Convert CSV literature data to WOS format for CiteSpace.')
parser.add_argument('input', help='Path to the input CSV file')
parser.add_argument('-o', '--output', default='output_wos.txt', help='Path to the output WOS text file')
args = parser.parse_args()
# 然后调用你的转换函数
convert_csv_to_wos(args.input, args.output)
这样,在终端里输入 python csv2wos.py my_literature.csv -o result.txt 就能直接运行,非常方便。
6. 避坑指南与经验之谈
最后,分享几个我在这个过程中踩过的坑和总结的经验,希望能帮你少走弯路。
第一个大坑:编码问题。 这是最常遇到的问题,没有之一。中文环境下的CSV文件,编码可能是GBK或UTF-8 with BOM。如果你用Python默认的utf-8读取GBK编码的文件,或者用utf-8读取带BOM的文件,开头可能会多出奇怪的字符(如\ufeff),导致字段名匹配失败。解决方案:在打开文件时,明确指定编码。对于可能带BOM的UTF-8文件,使用 encoding='utf-8-sig';对于其他情况,可以先用记事本打开CSV文件,点击“另存为”,在底部确认编码格式。
第二个坑:字段分隔符。 CSV虽然是“逗号分隔值”,但有时数据内本身就包含逗号(比如作者地址),这时导出软件可能会用引号将整个字段包起来。Python的csv模块能很好地处理这种情况。但如果你用pandas的read_csv,需要注意quotechar参数。最稳妥的方法是,先用文本编辑器打开CSV看一眼它的具体格式。
第三个坑:CiteSpace的严格性。 CiteSpace对WOS格式有一定容错,但并非无限。PT J和ER必须成对出现,且PT J必须单独一行。字段标签(如TI)和值之间的空格最好只有一个。文件末尾的EF有时不加也能运行,但为了规范最好加上。我建议在生成最终文件后,用WOS官方导出的一个小文件和你生成的文件进行对比,用文本比较工具(如VS Code的对比功能)查看差异,能帮你快速定位格式问题。
关于数据质量。 转换工具只能解决格式问题,解决不了数据本身的质量问题。比如,作者名不统一(“Wang, Xiao” vs “Xiao Wang”)、关键词的同义词和近义词(“machine learning” vs “ML”)、机构名称的缩写和全称,这些都会直接影响CiteSpace分析结果的准确性。格式转换是第一步,而数据清洗和标准化才是产出可靠分析结果更耗时、更关键的一步。 你可以在Python脚本中加入一些简单的清洗规则,比如用正则表达式统一作者姓名的格式,或者用词典映射来规范关键词。
整个过程走下来,你会发现,用Python脚本处理这类格式转换任务,核心思路并不复杂:理解目标格式 -> 解析源数据 -> 映射和转换 -> 输出。它最大的价值在于将你从繁琐、重复的机械劳动中解放出来,让你能更专注于分析本身。当你看到CiteSpace成功加载了你亲手转换的几千条数据,并开始绘制出知识图谱时,那种成就感,绝对比手动一条条粘贴复制要强得多。希望这个详细的流程和代码,能切实地帮到你。如果在实际操作中遇到新的问题,不妨多看看CiteSpace的官方文档和社区论坛,很多时候答案就在那里。
更多推荐



所有评论(0)