msn聊天记录 网上查看 合并MSN聊天记录的脚本(探索中)

                   
专业调查机构联系电话,13717775644。代您查询删除各类记录资料,请来电咨询。

上周回了一趟老家,没网上,只好做些不用上线的事。正好找了点时间来写合并MSN(WindowsLiveMessenger)的聊天记录的脚本。

MSN的聊天记录以XML文件的形式保存,默认保存在[color=blue]%MyDocuments%\MyReceivedFiles\username\History[/color]里。中文系统的话是教学保存在[color=blue]%我的文档%\我接受到的文件\username\历史记录[/color]里。保存路径可以在登录MSN后设置。

一般我在同一台机器上重装系统的话,会把聊天记录的目录转移到新装的系统上。但是我也经常要用不同的机器,偶尔也需要在不是我的机器上登录MSN。这么一来,聊天记录就散得到处都是了。每次整理数据的时候都想解决一下这个问题,不过每次都是懒了……

既然是XML,处理起来应该是非常方便的——不用自己从lexer和parser开始写解析用的教学。那么来看看一个MSN的聊天记录文件大概长什么样:



http://www.engadget.com/2009/05/30/sonys-psp-go-leaks-out-before-e3-is-obviously-a-go/

可以看到该文件引用了一个XSLT文件来做渲染。事实上在MSN里浏览聊天记录的时候显示的就是通过该XSLT转换过的XML记录。

这个XML文件的根节点是Log,属性包括FirstSessionID和LastSessionID两个。Log下面的子节点主要是一个或多个Message节点,其中的Session属性表示会话序号,前面提到根节点的两个属性对应文件中所有Session值的状况;也可能存在另外两种节点,下文会提到。

合并记录的关键就在于那些Session值,其它内容都不用修改,直接复制过来就行。脚本应该根据日期正确的把聊天记录按顺序排起来,并重新计算各个Message(和另外两种节点)中的Session值,然后更新Log的FirstSessionID/LastSessionID,最后写出文件。

用Ruby来处理XML文件,我教学习惯性选择用Nokogiri来做。这里用的版本是Nokogiri1.2.3。通过Nokogiri.parse得到一个Document对象doc后,通过doc/'Log/Message'就可以得到根节点下的所有Message节点了。我以为根节点下只有Message节点,试着写了个实验用脚本,发现合并了之后结果居然比其中一个源还要小,肯定出问题了。

于是就另外写了个脚本来看聊天记录的XML文件里根节点下到底有哪些类型的节点:

extract_node_names.rb:

require 'rubygems'
require 'nokogiri'

fname = ARGV[0]
doc = Nokogiri.parse File.read(fname)
log = doc.root
children = log.children

names = children.inject({}) do |acc, e|
n = e.node_name
acc[n] ||= e
acc
end

p names.keys
puts
names.keys.each do |k|
puts "#{k}:"
puts names[k].to_xml('gbk'), ''
end

对某个文件运行该脚本,结果是:

["Message", "Invitation", "InvitationResponse"]

Message:
SessionID="1">






现在有验证机制了么


Invitation:




C:\Documents and Settings\RednaxelaFX\Desktop\amazon090714.txt
RednaxelaFX sends C:\Documents and Settings\RednaxelaFX\Desktop\New File.txt


InvitationResponse:




C:\Documents and Settings\RednaxelaFX\Desktop\amazon090714.txt
Transfer of "New File.txt" is complete.

原来除了Message节点外还有Invitation和InvitationResponse两种节点,对应传输文件的信息。或许我还没碰到所有类型的节点……不过只要这些节点有Session和DateTime属性就能用同样的方式去处理,所以倒不用怎么担心。

OK,那就写个教学的脚本来解决这个合并记录的问题:

cat_msn_logs.rb:

require 'rubygems'
require 'nokogiri'
require 'fileutils'

# Convert a Nokogiri::XML::Document into
# an array of arrays, grouped by SessionID.
# The inner arrays begin with the DateTime of
# their first Element, used later for sorting.
def group_log_by_sid(xml_log)
xml_log.root.children.inject({}) {|acc, node|
sid = node['SessionID']
(acc[sid] ||= [ DateTime.parse(node['DateTime']) ]) << node
acc
}.values
end

# Join two arrays of Element groups into one,
# sorted by DateTime, and removing redundant
# entries.
def join_groups(grp1, grp2)
groups = (grp1 + grp2).
sort_by {|g| g[0] }.
inject([]) {|acc, g|
(acc.empty? || acc.last[0] != g[0]) ?
acc << g : acc }.
map {|g| g.shift; g }

# fix SessionIDs
groups.each_with_index do |grp, idx|
sid = (idx + 1).to_s
grp.each {|n| n['SessionID'] = sid }
end
end

# Save a Nokogiri::XML::Document to file,
# with no formatting, and UTF-8 encoding.
def save_log(xml_log, fpath)
File.open(fpath, 'w') do |f|
# need to pass in save_options to
# strip excessive whitespace/formatting
xml_log.write_to(
f, # io
'utf-8', # encoding
Nokogiri::XML::Node::SaveOptions::AS_XML)
end
end

# Join the two designated MSN logs into one.
def join_logs(fname, src_dir1, src_dir2, dest_dir)
log1, log2 = [ src_dir1, src_dir2 ].map do |d|
File.open(File.join(d, fname), 'r') do |f|
Nokogiri.parse f
end
end

nodes = join_groups(*(
[ log1, log2 ].map {|log| group_log_by_sid log }))

root = log1.root
root['FirstSessionID'] = '1'
root['LastSessionID'] = nodes.length.to_s
root.inner_html = ''
nodes.flatten.each {|n| root.add_child n }

save_log log1, File.join(dest_dir, fname)
end

# command-line arguments:
# src_dir1,
# src_dir2,
# dest_dir (optional, defaults to src_dir1)
#
# variables to keep track of:
# src_dir1 : String
# src_dir2 : String
# dest_dir : String
# fname : String
if __FILE__ == $0
src_dir1, src_dir2, dest_dir = ARGV.map {|p| File.expand_path p }
dest_dir ||= src_dir1
FileUtils.makedirs dest_dir

# check if files with the same name exist in both dirs,
src_entries1, src_entries2 = [ src_dir1, src_dir2 ].map do |d|
Dir.entries(d).grep(/\.xml/i)
end
in_both_dirs = src_entries1 & src_entries2
[
[ src_dir1, src_entries1 ],
[ src_dir2, src_entries2 ]
].each do |g|
src_dir = g[0]
entries = g[1]
unless src_dir.downcase == dest_dir.downcase
(entries - in_both_dirs).each do |f|
FileUtils.copy_file(
File.join(src_dir, f), # src
File.join(dest_dir, f), # dest
true # preserve
)
end
end
end
# disable GC due to memory leak issues in Nokogiri
GC.start
GC.disable
# otherwise, join the logs
in_both_dirs.each do |f|
print "processing #{f}..."
join_logs f, src_dir1, src_dir2, dest_dir
puts 'ok'
end
#GC.enable
end

__END__

# the following code may be used later in refactoring

# not used...
#~ def read_xml_file(fpath)
#~ Nokogiri.parse File.read(fpath)
#~ end

# not used...
#~ def log_session_range(xml_log)
#~ root = xml_log.root
#~ root['FirstSessionID'].to_i..root['LastSessionID'].to_i
#~ end

# not used...
#~ def node_sid(xml_node)
#~ xml_node['SessionID'].to_i
#~ end

# not used...
#~ def set_node_sid(xml_node, sid)
#~ xml_node['SessionID'] = sid.to_s
#~ end

# not used...
#~ def node_datetime(xml_node)
#~ DateTime.parse xml_node['DateTime']
#~ end

edge cases not handled:
/ - Archive(\d{8})\.xml/

(__END__之后的是我用来提醒自己用的东西……请忽略)

我用的Nokogiri1.2.3看来在包装libxml2的时候什么地方没弄好,我一开始试的时候只要多处理几个XML文件就会出现segfault。我觉得很纳闷,想了些办法后发现是GC过后就出问题。于是干脆暂时把GC禁用掉,那样segfault只会出现在所有XML文件都处理完了之后,也就不影响使用了。我一直怀疑我是不是有什么该调用的清理用方法没调用导致segfault,hmm

除了Nokogiri带来的问题外,在实际使用过程中还发现了一个问题:

我一直以为这些聊天记录的XML文件中Log的FirstSessionID总是1,而LastSessionID跟文件中出现的Session数一样。后来发现原来MSN会在文件超过2MB后开新的文件,并把原来的文件重命名为“original_filename_without_extension-ArchiveYYYYMMDD.xml”的形式。这样,新开的XML文件中FirstSessionID就不是从1开始了。很不幸我的记录里有一个同学的记录就超过了2M,害我手动处理了==

我觉得处理新开文件的情况挺麻烦的。首先我得把Archive文件和当前文件中的内容合并到一起,再跟另外一边的源合并到一起。然后要模仿MSN的教学,在写出的时候记录是否达到了2MB,达到则新开个文件继续写。但是怎么记录已写出的文件大小呢?难道我要在重新计算好各节点的Session值之后msn聊天记录 网上查看,添加回到根节点时对每个节点调用Nokogiri::XML::Element#to_s然后数byte数?好烦啊TT

本来想再把这个问题考虑进去msn聊天记录 网上查看,顺便重构一下再发出来的。想想还是先发个出来收集些建议再修改比较有效率。求改进建议


本文地址:http://www.tonghuadaicha.com//guanyuwomen/7924.html