R中for循环未完整处理XML脚本 解析系统日志仅捕获首条事件
问题描述
我正在编写解析系统日志类XML文件的例程,用于捕获登录与注销消息。我设置了多处检查点验证XML文件是否被完整读取,但打印dataframe时,仅能捕获到第一条事件。
原有代码如下:
LG.df <- read_xml("Syslog.xml") xml_ns(LG.df) NS<- xml_children(xml_children(LG.df)) EV<- "" DT<- "" AN<- "" AD<- "" WSN<- "" SA<- "" LogEvents.df<- data.frame(EV, DT, AN, AD, WSN, SA) print(LG.df) for (i in sequence(length(NS))) { DT<- NS[1] DT<- gsub("<DateTime>","",as.character(DT)) DT<- gsub("</DateTime>","",as.character(DT)) #print(DT) Pr<- NS[2] Pr<- gsub("<Priority>","",as.character(Pr)) Pr<- gsub("</Priority>","",as.character(Pr)) #print(Pr) SH<- NS[3] SH<- gsub("<Source_Host>","",as.character(SH)) SH<- gsub("</Source_Host>","",as.character(SH)) #print(SH) MT<- NS[4] MT<- gsub("<MessageText>","",as.character(MT)) MT<- gsub("</MessageText>","",as.character(MT)) #print(MT) EV<- str_match(MT, "\\d{4}(?=\\tMicrosoft-Windows-Security-Auditing)") #print(EV) AN<- str_match(MT, "(?<=Account Name:\\t\\t).*?(?=\\n)") SID<- str_match(MT, "(?<=Security ID:\\t\\t).*?(?=\\n)") AD<- str_match(MT, "(?<=Account Domain:\\t\\t).*?(?=\\n)") WSN<- str_match(MT, "(?<=Workstation Name:\\t\\t).*?(?=\\n)") SA<- str_match(MT, "(?<=Source Network Address:\\t\\t).*?(?=\\n)") if(EV == 4624){(LogEvents.df$EV <-(EV)) (LogEvents.df$DT<- (DT)) (LogEvents.df$AN<- (AN)) (LogEvents.df$AD<- (AD)) (LogEvents.df$WSN<- (WSN)) (LogEvents.df$SA<- (SA)) } else if(EV == 4634){(LogEvents.df$EV <-( EV))} (LogEvents.df$DT<- (DT)) (LogEvents.df$AN<- (AN)) (LogEvents.df$AD<- (AD)) (LogEvents.df$WSN<- (WSN)) (LogEvents.df$SA<- (SA)) }
错误原因
代码存在4个核心问题,导致只能拿到单条数据:
- 循环索引完全未生效:定义了迭代变量
i,但所有节点取值都写死为NS[1]/NS[2]/NS[3]/NS[4],每次循环都在取固定位置的第一个事件节点,从未遍历所有节点。 - 结果写入逻辑错误:初始创建的
LogEvents.df只有1行,循环中每次都是直接给整列赋值覆盖旧值,从未追加新行,最终只会保留最后一次写入的单条数据。 - 条件判断括号错位:
else if(EV == 4634)分支后,其余字段的赋值被写在判断分支外,括号配对错误,非目标事件也会错误覆盖数据。 - XML解析方式不规范:用
gsub正则替换标签提取值的容错性极差,一旦文本内容包含特殊字符、标签格式有细微变化就会解析失败,应该使用xml2包自带的节点取值函数。
修正方案
修正后的可运行代码如下:
library(xml2) library(stringr) # 读取XML文件,若存在命名空间解析问题可加一行xml_ns_strip(LG)移除命名空间 LG <- read_xml("Syslog.xml") # 替换XPath为你实际日志里的事件节点路径,这里默认是Event节点 all_events <- xml_find_all(LG, ".//Event") # 初始化空结果表,不提前插入空行 LogEvents.df <- data.frame( EV = character(), DT = character(), AN = character(), AD = character(), WSN = character(), SA = character(), stringsAsFactors = FALSE ) # 逐个遍历事件节点 for (event_node in all_events) { # 直接用xml_text提取节点文本,放弃gsub硬替换标签的写法 DT <- xml_text(xml_find_first(event_node, "./DateTime")) MT <- xml_text(xml_find_first(event_node, "./MessageText")) # 提取事件ID,str_match返回矩阵,取[,1]拿到匹配结果 EV <- str_match(MT, "\\d{4}(?=\\tMicrosoft-Windows-Security-Auditing)")[,1] # 非登录(4624)/注销(4634)事件直接跳过 if (!EV %in% c("4624", "4634") | is.na(EV)) next # 提取其余字段 AN <- str_match(MT, "(?<=Account Name:\\t\\t).*?(?=\\n)")[,1] AD <- str_match(MT, "(?<=Account Domain:\\t\\t).*?(?=\\n)")[,1] WSN <- str_match(MT, "(?<=Workstation Name:\\t\\t).*?(?=\\n)")[,1] SA <- str_match(MT, "(?<=Source Network Address:\\t\\t).*?(?=\\n)")[,1] # 追加新行到结果表,不覆盖原有列 LogEvents.df <- rbind( LogEvents.df, data.frame( EV = EV, DT = DT, AN = AN, AD = AD, WSN = WSN, SA = SA, stringsAsFactors = FALSE ) ) } # 查看最终解析结果 View(LogEvents.df)
修正要点说明:
- 放弃写死索引的取值方式,直接遍历每个独立事件节点,用
xml_text()提取节点文本,解析容错性更高。 - 初始化空结果数据框,匹配到目标事件时用
rbind()追加新行,避免整列覆盖的问题。 - 修正条件判断逻辑,非目标事件直接跳过,避免无效数据写入。
- 处理
str_match()返回矩阵的问题,统一取第一列匹配值,避免后续类型判断出错。
内容的提问来源于stack exchange,提问作者Bill
相关产品推荐
相关产品推荐

