ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

R语言批量抓取TCMSP中药靶点基因:从数据获取到清洗的自动化方案

R语言批量抓取TCMSP中药靶点基因:从数据获取到清洗的自动化方案 1. 中药靶点研究的数据获取困境与破局思路做中药网络药理学的人几乎都绕不开TCMSP这个数据库。它全称是Traditional Chinese Medicine Systems Pharmacology Database and Analysis Platform是目前中药系统药理学领域引用率最高的数据源之一收录了大量中药材的化学成分、ADME参数以及对应的靶点蛋白信息。但凡你要做中药-成分-靶点-通路这类网络分析第一步就是把目标中药的靶点基因列表拿到手。问题就出在这个拿到手上。TCMSP的网页界面设计得比较传统查询单味中药时你需要手动选择药材名、点击搜索、等待结果加载、找到靶点信息区域、逐条复制粘贴到本地文件。如果只做一两味药忍一忍也就过去了。但实际课题里动辄十几味、几十味药做对比分析甚至要做复方中所有药材的合并靶点集手动操作就变成了纯粹的体力消耗。我见过有同学复制粘贴了一整个下午最后发现漏了两味药的数据又得从头核对。更麻烦的是TCMSP的靶点信息展示方式并不总是规整的表格。有些药材的靶点列表嵌在详情页里有些需要展开折叠区域有些字段的命名在不同页面之间还有细微差异。手动整理时格式不统一、基因名大小写混乱、重复项遗漏等问题层出不穷。这些看似琐碎的细节到了下游做Venny图取交集、做STRING网络构建的时候就会变成致命的错误来源。所以用脚本自动化完成这个抓取和整理过程本质上解决的是三个层面的问题效率问题批量处理替代逐条操作、准确性问题程序化提取避免人为遗漏和格式错误、可复现性问题脚本留存下来换一批药材或者换一个时间点重新跑结果一致。R语言在这个场景下是一个很自然的选择因为做网络药理学的人后续大概率要用R做统计分析、画图整个流程用同一套工具链数据格式的衔接最顺畅。这篇文章面向的是有基本R语言操作经验、正在做或准备做中药网络药理学研究的同学。如果你还没装R和RStudio文章里也会提到环境准备的关键点。核心目标很明确给你一套能直接跑通的R脚本方案把TCMSP上目标中药的靶点基因批量抓下来整理成干净的、可以直接进入下游分析的数据表。2. 整体方案设计与技术选型考量2.1 为什么选R而不是Python或其他工具这个问题的答案其实很实际。做中药网络药理学的主流工具链里R的出场率极高——clusterProfiler做富集分析、igraph做网络可视化、limma做差异表达这些包都是R生态里的。如果你的数据抓取环节也用R完成那么从原始数据到分析结果之间就不需要跨语言转换数据框的列名、类型、编码方式从头到尾保持一致省去了很多格式对齐的麻烦。另一个考虑是R的rvest包在网页解析方面的成熟度。rvest是Hadley Wickham团队开发的语法设计跟dplyr一脉相承用管道操作符串联起来非常直观。对于TCMSP这种结构相对固定的页面rvest配合CSS选择器或者XPath就能精准定位到目标元素。相比之下Python的BeautifulSoup虽然也很强大但如果你本身R更熟为了抓数据专门去搭Python环境、学一套新语法投入产出比不划算。当然如果你的团队统一用Python做数据分析那用requestsBeautifulSoup或者selenium也是完全可行的。工具选择的核心原则是跟你下游分析的工具链保持一致减少数据流转环节的摩擦。2.2 抓取策略直接请求还是模拟浏览器TCMSP的页面加载方式决定了抓取策略的选择。经过实际测试TCMSP的药材详情页和靶点列表页大部分内容是服务端渲染的也就是说你直接向目标URL发送HTTP请求返回的HTML里就包含了靶点信息。这意味着不需要动用Selenium或者Playwright这类浏览器自动化工具用httr发请求、rvest解析HTML就够了。但这里有几个关键细节需要注意。第一TCMSP的查询接口可能对请求频率有限制如果你在循环里不加延时地连续请求几十次有可能会被临时限制访问。第二某些药材的靶点数据是通过AJAX异步加载的直接请求页面URL拿不到完整内容需要找到实际的API接口地址。第三请求头里的User-Agent字段最好设置成常见浏览器的标识避免被识别为异常流量。我的建议是采用先探测、后批量的策略先用一两味药做测试确认请求能正常返回数据、解析规则能正确提取目标字段再扩展到全量药材列表。这样即使中途遇到问题排查范围也可控。2.3 数据整理的核心逻辑抓取只是第一步真正决定数据能不能用的是整理环节。TCMSP返回的靶点信息通常包含以下几类字段靶点名称可能是基因符号或蛋白名称、靶点ID如UniProt ID、以及与该靶点相关的成分信息。不同药材的数据结构可能略有差异比如有的药材靶点列表里包含多个成分对应同一靶点的情况有的则是去重后的靶点集合。整理逻辑需要明确几个决策点是否保留成分-靶点的对应关系如果后续要做成分-靶点网络就需要保留如果只做靶点集合分析可以去重、基因名统一用哪种命名规范推荐用HGNC官方基因符号全大写、多味药的靶点如何合并是取并集还是保留药材来源标记。这些决策没有标准答案取决于你的研究设计但脚本里需要把这些选项做成可配置的参数方便不同课题复用。3. 环境准备与核心工具包配置3.1 R与RStudio的安装要点如果你还没有R环境直接去R语言官网下载对应操作系统的安装包。Windows用户注意选择正确的版本64位系统选x64安装路径建议不要包含中文和空格避免后续包安装时出现路径解析问题。RStudio是IDE不是必须的但强烈建议装上代码编辑、变量查看、绘图预览的体验比原生R控制台好太多。安装完R之后RStudio要单独下载安装。两者的关系是R是引擎RStudio是驾驶舱。你可以在RStudio里切换不同版本的R也可以在不打开RStudio的情况下直接用R命令行运行脚本但日常开发肯定是用RStudio更顺手。3.2 核心R包的安装与加载这个项目需要用到几个核心包各自的分工如下包名用途安装命令httr发送HTTP请求获取网页内容install.packages(httr)rvest解析HTML提取目标元素install.packages(rvest)xml2rvest的底层依赖处理XML/HTMLinstall.packages(xml2)dplyr数据框操作清洗整理数据install.packages(dplyr)stringr字符串处理正则匹配install.packages(stringr)purrr函数式编程批量循环处理install.packages(purrr)readr读写CSV等格式文件install.packages(readr)安装时如果遇到某个包下载失败大概率是网络问题可以尝试换CRAN镜像源。在RStudio里可以通过Tools Global Options Packages CRAN mirror来切换国内用户建议选离自己地理位置近的镜像。安装完成后在脚本开头统一加载library(httr) library(rvest) library(xml2) library(dplyr) library(stringr) library(purrr) library(readr)注意如果你的R版本较老某些包可能要求更高的R版本。建议至少使用R 4.0以上版本避免因为版本兼容性问题卡在安装环节。3.3 工作目录与文件结构规划在开始写代码之前先把项目文件夹结构规划好。我习惯的结构是这样的TCMSP_Scraping/ ├── scripts/ │ └── tcmsp_scraper.R # 主脚本 ├── data/ │ ├── raw/ # 原始抓取结果 │ └── processed/ # 清洗后的数据 ├── output/ │ └── logs/ # 运行日志 └── README.md # 项目说明在R脚本里用setwd()设置工作目录或者更推荐的做法是用RStudio的Project功能把整个文件夹作为一个项目打开这样工作目录会自动设定脚本的可移植性也更好。4. 核心抓取逻辑的逐层拆解4.1 理解TCMSP的页面结构与请求方式要抓数据先得搞清楚数据在哪里。打开TCMSP网站搜索一味药材比如当归进入详情页后你会看到几个信息区块药材基本信息、成分列表、靶点信息、相关疾病等。靶点信息通常以列表或表格形式呈现每个靶点包含名称、ID等字段。关键问题是这些靶点数据是直接嵌在详情页的HTML里还是通过单独的接口异步加载的判断方法很简单在浏览器里右键查看页面源代码不是检查元素是查看网页源代码搜索靶点名称中的关键词。如果能在源代码里找到说明是服务端渲染直接请求页面URL即可如果找不到就需要打开浏览器的开发者工具在Network面板里找XHR请求定位实际的API地址。根据我的实际测试TCMSP的靶点数据大部分情况下是包含在详情页HTML中的但页面结构可能随时间调整。所以脚本里需要把CSS选择器或XPath路径做成可配置的变量方便页面改版时快速调整。4.2 构建请求函数稳健地获取页面内容直接写一个函数来封装请求逻辑把超时设置、重试机制、请求头伪装都考虑进去fetch_page - function(url, max_retries 3, delay 2) { for (i in seq_len(max_retries)) { tryCatch({ response - GET( url, user_agent(Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36), timeout(30) ) if (status_code(response) 200) { return(content(response, text, encoding UTF-8)) } else { message(sprintf(请求返回状态码 %d第 %d 次重试, status_code(response), i)) } }, error function(e) { message(sprintf(请求出错%s第 %d 次重试, e$message, i)) }) Sys.sleep(delay * i) # 递增延时避免频繁请求 } return(NULL) }这个函数的设计要点重试机制应对临时网络波动递增延时避免触发频率限制User-Agent伪装降低被拦截概率超时设置防止单次请求卡死整个脚本。这些细节在批量抓取时非常重要因为只要有一味药请求失败整个流程就可能中断。4.3 解析靶点信息定位与提取拿到HTML内容后用rvest解析。假设靶点信息在一个带有特定class的表格或列表中提取逻辑大致如下parse_targets - function(html_content, herb_name) { page - read_html(html_content) # 定位靶点信息区域选择器需要根据实际页面结构调整 target_nodes - page %% html_nodes(.target-list li) # 示例选择器 if (length(target_nodes) 0) { warning(sprintf(%s 未找到靶点信息请检查选择器, herb_name)) return(NULL) } targets - target_nodes %% html_text(trim TRUE) %% str_trim() # 提取基因名和靶点ID根据实际文本格式调整正则 result - data.frame( herb herb_name, target_raw targets, stringsAsFactors FALSE ) return(result) }这里最关键的是选择器的准确性。你需要用浏览器的开发者工具定位到靶点列表所在的HTML元素记下它的class或id然后填入html_nodes()。如果页面结构复杂可能需要多层嵌套选择比如.herb-detail .target-section table tbody tr这样的路径。实操心得选择器不要写得太死。比如不要用nth-child(3)这种依赖位置的选择器因为页面微调就可能失效。优先用有语义的class名或id。如果实在没有合适的class用XPath结合文本内容定位也是一种办法。4.4 批量循环遍历药材列表单味药的抓取逻辑跑通后扩展到批量就简单了——把药材名列表读进来用purrr::map_dfr()循环处理herb_list - c(当归, 黄芪, 人参, 甘草, 川芎) all_targets - map_dfr(herb_list, function(herb) { message(sprintf(正在抓取%s, herb)) # 构建查询URL需要根据TCMSP的实际URL规则调整 query_url - sprintf(https://tcmsp-e.com/herb_detail.php?herb%s, URLencode(herb)) html_content - fetch_page(query_url) if (is.null(html_content)) { warning(sprintf(%s 抓取失败跳过, herb)) return(NULL) } result - parse_targets(html_content, herb) Sys.sleep(1.5) # 每味药之间间隔1.5秒 return(result) })map_dfr()的好处是自动把每次返回的数据框按行合并省去了手动rbind()的麻烦。如果某味药返回NULL它会自动跳过不会导致整个循环崩溃。5. 数据清洗与格式标准化5.1 基因名规范化处理TCMSP返回的靶点名称格式可能不统一有的用基因符号如PTGS2有的用蛋白名称如Prostaglandin G/H synthase 2有的还带有物种前缀。下游分析通常需要统一的基因符号所以清洗环节要做几件事第一去除多余空白和特殊字符。用str_trim()去掉首尾空格用str_replace_all()清理不可见字符。第二统一大小写。人类基因符号的规范是全大写所以用str_to_upper()统一转换。第三处理别名。有些靶点可能有多个别名需要映射到官方符号。这一步如果数据量不大可以手动维护一个映射表如果数据量大可以考虑用org.Hs.eg.db等注释包来做ID转换。clean_targets - all_targets %% mutate( target_clean target_raw %% str_trim() %% str_to_upper() %% str_replace_all([^A-Z0-9-], ) # 只保留字母、数字和连字符 ) %% filter(!is.na(target_clean), target_clean ! )5.2 去重与多药合并策略如果研究设计是多味药的靶点取并集那么清洗后需要去重unique_targets - clean_targets %% distinct(target_clean, .keep_all TRUE)但如果需要保留哪味药对应哪些靶点的信息比如做药材-靶点二分网络就不能简单去重而是要用列表列list-column或者长格式来保留对应关系herb_target_summary - clean_targets %% group_by(herb) %% summarise( target_count n_distinct(target_clean), targets paste(unique(target_clean), collapse ; ) )这种汇总表在写论文时特别有用可以直接放进补充材料。5.3 输出为下游可用的格式清洗完的数据建议同时输出两种格式CSV用于Excel查看和手动核对RDS用于R脚本之间的数据传递RDS能完整保留数据类型和结构读取速度也更快。write_csv(clean_targets, data/processed/tcmsp_targets_clean.csv) saveRDS(clean_targets, data/processed/tcmsp_targets_clean.rds)注意CSV文件用Excel打开时如果基因名里包含类似日期格式的字符串比如MAR1可能被识别为日期会出现显示错误。这是Excel的自动类型推断导致的不是数据本身的问题。可以用read_csv()在R里查看或者在Excel里用数据导入向导指定列类型为文本。6. 常见问题与排查技巧实录6.1 请求被拒绝或返回空内容这是最常见的问题。表现是fetch_page()返回NULL或者返回的HTML里找不到靶点信息。排查思路按以下顺序进行第一步检查URL是否正确。手动在浏览器里打开同样的URL看能否正常访问。如果浏览器能打开但R不行说明是请求头或频率问题。第二步检查请求频率。如果连续快速请求多次后被拒绝说明触发了频率限制。解决办法是增大Sys.sleep()的间隔或者把请求分散到不同时间段执行。第三步检查页面结构是否变化。如果URL能访问、频率也不高但就是解析不到数据很可能是TCMSP改版了CSS选择器失效。这时候需要重新用开发者工具定位目标元素更新选择器。6.2 中文编码问题TCMSP的页面编码可能是UTF-8也可能是GBK。如果抓下来的中文显示为乱码需要在content()函数里指定正确的编码content(response, text, encoding UTF-8)如果UTF-8不行试试GBK或GB2312。判断方法看返回的HTML源码里meta charset...标签的值。6.3 靶点名称格式不一致不同药材的靶点列表可能用不同的命名体系。有的用基因符号有的用UniProt ID有的用蛋白全称。这种情况下单纯靠字符串清洗无法统一需要做ID映射。推荐的做法是先把所有靶点名称收集起来去重后得到一个唯一列表然后手动或半自动地映射到统一的基因符号。如果靶点数量在几百个以内手动核对虽然费时但最可靠如果上千个可以考虑用注释包批量转换但转换后一定要抽样验证准确性。6.4 脚本运行中断后的断点续跑批量抓取几十味药时如果跑到一半因为网络问题中断重新跑一遍会浪费时间。解决办法是在循环里加入已抓取则跳过的逻辑if (file.exists(sprintf(data/raw/%s.rds, herb))) { message(sprintf(%s 已存在跳过, herb)) next }每味药抓取成功后立即保存为单独的RDS文件这样即使中断下次运行也能从断点继续。6.5 常见问题速查表问题现象可能原因排查方法解决方案返回NULLURL错误或网络不通浏览器手动访问同一URL修正URL检查网络返回403/429请求频率过高查看status_code增大延时降低频率解析结果为空选择器失效查看页面源代码更新CSS选择器中文乱码编码不匹配查看meta charset指定正确encoding基因名混乱命名体系不统一抽样查看原始数据做ID映射转换脚本中途崩溃单次请求异常查看错误信息加tryCatch和重试7. 完整脚本结构与可复用性设计7.1 脚本的模块化组织把整个流程拆成几个独立的函数模块每个模块负责一个明确的任务fetch_page()负责请求parse_targets()负责解析clean_targets()负责清洗save_results()负责输出。主流程用一个main()函数串联起来。这样的好处是调试时可以单独测试某个模块修改时也只影响对应的部分不会牵一发而动全身。main - function(herb_list, output_dir data/processed) { # 1. 批量抓取 raw_data - batch_fetch(herb_list) # 2. 解析 parsed_data - parse_all(raw_data) # 3. 清洗 cleaned_data - clean_targets(parsed_data) # 4. 保存 save_results(cleaned_data, output_dir) # 5. 输出摘要 print_summary(cleaned_data) return(cleaned_data) }7.2 参数配置的外部化把药材列表、URL模板、选择器、延时时间这些容易变化的参数统一放在脚本开头的配置区域或者单独写一个config.R文件。这样换一个课题时只需要改配置不用动核心逻辑。# 配置区域 config - list( herb_list c(当归, 黄芪, 人参), base_url https://tcmsp-e.com/herb_detail.php, target_selector .target-list li, request_delay 1.5, max_retries 3, output_dir data/processed ) # 7.3 日志记录与运行监控批量任务最好加上日志记录把每味药的抓取状态、耗时、靶点数量都写进日志文件。这样出问题时可以快速定位是哪一步、哪味药出的错。log_message - function(msg, log_file output/logs/scraping_log.txt) { timestamp - format(Sys.time(), %Y-%m-%d %H:%M:%S) write(sprintf([%s] %s, timestamp, msg), file log_file, append TRUE) }7.4 扩展到其他类似数据库的思路这套请求-解析-清洗-输出的框架不仅适用于TCMSP换成其他中药数据库如BATMAN-TCM、SymMap等或者类似的生物信息学数据库只需要修改URL模板和解析选择器核心逻辑完全可以复用。这也是为什么建议把代码模块化——一次投入多处受益。8. 我踩过的坑与实操建议说几个实际做的时候容易忽略的点。第一不要等到脚本写完才测试。我的习惯是每写一个函数就单独跑一次用一两味药的数据验证输出是否符合预期。等全部写完再跑一旦出错排查范围太大。第二抓取前先手动确认目标数据的位置。打开开发者工具用元素选择器点一下靶点列表看看它的HTML结构是什么样的。这一步花五分钟能省掉后面半小时的调试。第三保存原始HTML。解析失败时如果有原始HTML留存可以离线调试解析逻辑不用反复请求服务器。我通常会把每味药的原始HTML存一份到data/raw/html/目录下。第四注意TCMSP的更新。数据库会不定期更新药材的靶点列表可能变化。如果你的研究有明确的时间节点要求建议在脚本里记录抓取日期并在论文方法部分注明数据获取时间。第五基因名映射要留痕。如果做了ID转换或别名映射一定要把原始名称和转换后名称的对应关系保存下来。审稿人问起来的时候你能说清楚每一个基因是怎么来的。最后分享一个提高效率的小技巧如果你要抓的药材数量很多可以把药材列表分批比如每批10味药跑完一批检查一下日志和输出确认没问题再跑下一批。这样即使某一批出了问题也不会影响已经完成的部分。
返回列表