本帖最后由 风音洛洛 于 2012-6-27 17:58 编辑
Bilibili小工具③ 弹幕文件的获取和解析
现在说下弹幕的获取和解析.
弹幕文件地址可以说是公开的,直接下载就行.
http://www.bilibili.tv/dm,<VID号>?r=<三位随机数>
但是在下载过程中我遇到了个问题
似乎这里下载下来的文件是经过压缩的,打开全是乱码,但是浏览器却可以正常访问.
我在网上找了很久也没找到这种压缩的方法.
在这里纠结了很久.最后用了一种方法解决.先看下代码
[mw_shl_code=csharp,true] /// <summary>
/// 下载并解析视频的弹幕文件,此为异步下载,只有当触发DocumentCompleted事件后 才会自动赋值给相应变量
/// </summary>
/// <returns>返回值是弹幕集合的引用</returns>
public List<BiliComment> ResolveComment()
{
if (this.vid == null | this.vid == "") return null;
CommentXml = null;
string ResultXml = string.Empty;
Random r = new Random();
string uri = string.Format(@"http://www.bilibili.tv/dm,{0}", this.vid) + "?r=" + r.Next(100, 888);
System.Windows.Forms.WebBrowser wb = new System.Windows.Forms.WebBrowser();
wb.Url = new Uri(uri);
wb.DocumentCompleted += (sender,e) =>
{
var we = (System.Windows.Forms.WebBrowser)sender;
mshtml.HTMLDocument html = (mshtml.HTMLDocument)we.Document.DomDocument;
CommentXml = html.body.innerText;
we.Dispose();
this.Comments = BiliComment.ReadXmlbili(this.CommentXml);
OnCommentReadComplete(this, new EventArgs());
};
return this.Comments;
}[/mw_shl_code]
因为浏览器可以正常访问,所以我干脆就新建一个临时的WebBrowser 浏览器控件(并不显示),直接访问那个地址,然后直接获取浏览器里面的内容.
因为构造浏览器和打开页面需要一定的时间,
因为WebBrowser是异步的,他新开了一个线程去获取页面,所以我们不能直接读取他里面的内容,要等他加载完以后才能去获取里面的内容.
WebBrowser提供了读取完成的事件DocumentCompleted,
我们在这个事件的响应函数里面完成获取内容和解析xml文件(BiliComment.ReadXmlbili函数),
并同时触发一个我们自己定义的事件通知弹幕文件已经下载和解析完成,可以用来显示了.
因为这个步骤,可以明显的发现弹幕信息比视频信息和下载地址后出来,但是期间界面并没有假死.
这里的响应函数,我用了匿名函数,或者说Lambda表达式,
因为这个响应函数只要被调用一次,没有必要为他单独写一个函数名,
而且,响应函数放到事件之后有助于代码的理解.
Lambda表达式的值就是一个委托,这个委托会直接调用后面我们所定义的函数,格式是这样:
(参数表) => { //函数体 }
其中参数表如果没有就可以不写.
当然参数要和该委托的类型和数量一样.
关于Lambda和委托,我也没有办法解释得很清楚,大家可以去网上看看.
如果不懂,这里就直接理解为,当事件完成之后,执行这里面的语句.
关于获取浏览器里面的内容,我们需要HTMLDocument接口,
这是一个COM的接口,不是.net的,需要引用Windows核心DLL---MSHTML.
引用的话也很方便,直接引用那点右键,找到COM里面的MSHTML就行了.
WebBrowser控件提供了个属性(WebBrowser.Document.DomDocument)供我们获取HTMLDocument接口.
然后就是获取浏览器的内容,直接读取HTMLDocument. body.innerText即可.
但是要注意,因为我们是读取的纯文本信息,我发现这些信息不是正常的xml文件,还多了几个符号.
所以我们用字符串的办法解析这个文件.
这个文件大概是这样:
[mw_shl_code=xml,true]<i>
<chatserver>chat.bilibili.tv</chatserver>
<chatid>430356</chatid>
<source>k-v</source>
<d p="19.700000762939,1,25,16777215,1337526026,0,1b44594d,93098268">SF?</d>
<d p="70.400001525879,1,25,16777215,1337526077,0,1b44594d,93098529">自带字幕。。居然自带字幕。。</d>
<d p="291.79998779297,1,18,16711680,1337526502,0,d8bd8185,93100422">让我怀念了秋之回忆2的歌、、、、好怀念。。。</d>
<d p="170.5,1,25,16777215,1337526729,0,5527797a,93101359">自带字幕啊。。</d>
<d p="0,1,25,16777215,1337526738,0,1e324fbb,93101396">额咧~~~看成浪曲十首才点进来的</d>
<d p="239.80000305176,1,25,16777215,1337526798,0,5527797a,93101666">浪曲是什么概念涅0.0</d>
<d p="835.70001220703,1,25,16777215,1337526845,0,1b44594d,93101859">浪曲。。神眼神</d>
<d p="31.39999961853,1,25,16777215,1337527624,0,a8774bdd,93105137">这个..没弹幕啊</d>
<d p="1221.1999511719,1,25,16777215,1337528735,0,40063ab1,93108799">好吧...我要睡着了</d>
<d p="2722.1000976562,1,25,16777215,1337528737,0,1b44594d,93108805">元首日常自动脑补神OP。。</d>
<d p="43.900001525879,1,25,16777215,1337529041,0,D26cc162,93109717">你</d>
<d p="45.799999237061,1,25,16777215,1337530339,0,587238a0,93113212">自带弹幕不科学。。。</d>
<d p="589.09997558594,1,25,16777215,1337530539,0,cf380ae7,93113718">letter song</d>
........................
<d p="1011.5,5,25,65280,1339330338,0,2eb0098d,98152984">我会跟你说,我在做作业吗/n</d>
<d p="51.599998474121,1,25,16777215,1339492134,0,69da858a,98633980">优酷</d>
<d p="2727.3000488281,1,25,16777215,1339578550,0,8579040d,98888172">- -</d>
<d p="583.79998779297,1,25,16777215,1339578757,0,1f87a6f5,98888958">letter song</d>
<d p="2459.6999511719,5,25,16711935,1339580704,0,1f87a6f5,98896654">f/z!!!!!</d>
<d p="6.8000001907349,1,25,16777215,1339582719,0,b417e6fc,98905811">·-·必须路过听歌</d>
<d p="88.599998474121,1,25,16777215,1339582801,0,b417e6fc,98906186">这种歌,用来发呆真是太棒了</d>
<d p="1390.4000244141,1,25,16777215,1339584107,0,b417e6fc,98912035">·-·好赞的说</d>
<d p="237.80000305176,1,25,16777215,1339653040,0,5fc90fca,99081662">- -无聊打算重制下这个视频</d>
</i>[/mw_shl_code]
这个处理大家应该很熟悉了,先分行,每行分别搜索和读取.
如果对这里的字符串处理有什么不懂,可以回帖询问.
然后就返回了一个List<BiliComment>,这就是这个视频所有的弹幕和每条弹幕的信息.
BiliComment包括以下信息(只发了有用的一部分)
代码如下
[mw_shl_code=csharp,true] /// <summary>
/// 解析弹幕xml
/// </summary>
/// <param name="xml">所需解析的xml文件</param>
/// <returns>弹幕集合</returns>
public static List<BiliComment> ReadXmlbili(string xml)
{
List<BiliComment> re = new List<BiliComment>();
try
{
string[] Lines = xml.Split('\n');
foreach (var s in Lines)
{
try
{
if (s.IndexOf(" <d p=\"") == 0)
{
var index = s.IndexOf('"');
var CommentProperty = s.Substring(index + 1);
index = CommentProperty.IndexOf('"');
CommentProperty = CommentProperty.Substring(0, index);
string CommentMessage = s.Substring(index + s.IndexOf('"') + 3, s.IndexOf(@"</d>") - index - 3 - s.IndexOf('"'));
BiliComment comm;
string[] vs = CommentProperty.Split(',');
float playTime = float.Parse(vs[0]);
int fontsize = int.Parse(vs[2]);
int color = int.Parse(vs[3]);
DateTime date = DateFromLong(long.Parse(vs[4]));
int mode = int.Parse(vs[1]);
string user = vs[6];
string message = CommentMessage;
comm = new BiliComment();
comm.PlayTime = playTime;
comm.Fontsize = fontsize;
comm.Color = color;
comm.Date = date;
comm.Mode = mode;
comm.User = user;
comm.Message = message;
re.Add(comm);
}
}
catch
{
continue;
}
}
}
catch
{
System.Windows.MessageBox.Show("XML文件解析错误");
}
return re;
}[/mw_shl_code]
可以看到包括的信息还是很多的,但是我们并不需要显示这么多,而且里面有的属性也不适合显示,
所以我们需要建立一个专门用来显示弹幕信息的类, CommentViewItem ,这个类的定义很简单
[mw_shl_code=csharp,true]/// <summary>
/// 用来显示弹幕的对象
/// </summary>
class CommentViewItem
{
public float 时间
{ get; set; }
public string 内容
{ get; set; }
public DateTime 发送时间
{ get; set; }
public string 颜色
{ get; set; }
public int 字体大小
{ get; set; }
public string 弹幕模式
{ get; set; }
public string 发送者ID
{ get; set; }
public CommentViewItem(BiliComment bc)
{
this.时间 = bc.PlayTime;
this.内容 = bc.Message;
this.发送时间 = bc.Date;
this.颜色 ="#"+ Convert.ToString(bc.Color, 16).ToUpper();
this.字体大小 = bc.Fontsize;
switch (bc.Mode)
{
case 1: this.弹幕模式 = "滚动弹幕"; break;
case 4: this.弹幕模式 = "底端固定弹幕"; break;
case 5: this.弹幕模式 = "顶端固定弹幕"; break;
case 6: this.弹幕模式 = "逆向滚动弹幕"; break;
case 7: this.弹幕模式 = "定位弹幕"; break;
default: this.弹幕模式 = ""; break;
}
this.发送者ID = bc.User;
}
}[/mw_shl_code]
可以看到我这里用了中文命名,虽然不值得提倡,但是也是为了方便,
因为DataGrid显示数据的时候,就是以属性名为列标题(也是按照定义属性的顺序),
一般来说是要自己定义DataGrid的显示模板的,但是这里偷懒了.
这里面颜色是本身是16进制ARGB颜色转成的十进制数,这里我把它转回去了,好看一点.当然也可以通过判断这个值,直接给中文.
绑定的方式如下,这里顺便把下载地址的也绑定了,还更新了消息框
[mw_shl_code=csharp,true]
this.dg_MainDataGrid.ItemsSource = from c in ((BilibiliVideo)sender2).Comments
orderby c.Date
select new CommentViewItem(c);
this.dg_DownAdress.ItemsSource =((BilibiliVideo)sender2).DownAdressLst.Select(x => new { 下载地址 = x }).ToList();
this.tb_AddVideoInfo.Text = "弹幕解析完成";[/mw_shl_code]
这里用了集合类的Linq,这个我也才学会,
大意就是从Comments(弹幕集合)里面取出所有的弹幕(这里面的c),
然后按照发送日期排序(Date),这里是顺序orderby,然后对于每一个弹幕,都建立一个CommentViewItem (new CommentViewItem(c)),
并把所有的CommentViewItem都放在一个集合里面,再这个集合绑定到DataGrid上.
这个模式好像就是Long神有段时间天天跟我强调,要理解啊,要多用啊,的MVVM(Model-View-ViewModel)框架.
Model----数据(这里是Comments集合)
View----显示(也就是界面,例如这里的DataGrid)
ViewModel----显示模型(这里的CommentViewItem)
WPF/SilverLight经常要使用这种框架.
什么!你不知道Long神是谁?我就发张图,不解释
那么弹幕文件的下载和解析就完了.
|